swadhin biswas Ok batman · 7/28/2025
fb9342e
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
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#!/usr/bin/env python3
"""
LaxyFile Uninstaller
This script removes LaxyFile installation and cleans up associated files.
"""
import os
import sys
import shutil
import json
import argparse
from pathlib import Path
from typing import Dict, List, Any
class LaxyFileUninstaller:
"""LaxyFile uninstaller"""
def __init__(self, manifest_path: Path, verbose: bool = False):
self.manifest_path = Path(manifest_path)
self.verbose = verbose
self.manifest = self._load_manifest()
if not self.manifest:
raise Exception(f"Could not load installation manifest: {manifest_path}")
print("LaxyFile Uninstaller")
print(f"Installation directory: {self.manifest['install_dir']}")
print()
def _log(self, message: str, level: str = 'INFO'):
"""Log message"""
if self.verbose or level in ['ERROR', 'WARNING']:
print(f"[{level}] {message}")
def _load_manifest(self) -> Dict[str, Any]:
"""Load installation manifest"""
try:
if not self.manifest_path.exists():
return None
with open(self.manifest_path, 'r') as f:
return json.load(f)
except Exception as e:
print(f"Error loading manifest: {e}")
return None
def remove_installed_files(self) -> bool:
"""Remove installed files"""
self._log("Removing installed files...")
success = True
removed_count = 0
for file_path_str in self.manifest.get('installed_files', []):
try:
file_path = Path(file_path_str)
if file_path.exists():
if file_path.is_file() or file_path.is_symlink():
file_path.unlink()
removed_count += 1
self._log(f"Removed file: {file_path}")
elif file_path.is_dir():
shutil.rmtree(file_path)
removed_count += 1
self._log(f"Removed directory: {file_path}")
except Exception as e:
self._log(f"Failed to remove {file_path_str}: {e}", 'ERROR')
success = False
self._log(f"Removed {removed_count} files/directories")
return success
def remove_directories(self) -> bool:
"""Remove created directories"""
self._log("Removing installation directories...")
success = True
removed_count = 0
# Sort directories by depth (deepest first)
directories = sorted(
self.manifest.get('created_directories', []),
key=lambda x: len(Path(x).parts),
reverse=True
)
for dir_path_str in directories:
try:
dir_path = Path(dir_path_str)
if dir_path.exists() and dir_path.is_dir():
# Only remove if empty or if it's the main install directory
if (not any(dir_path.iterdir()) or
str(dir_path) == self.manifest['install_dir']):
shutil.rmtree(dir_path)
removed_count += 1
self._log(f"Removed directory: {dir_path}")
else:
self._log(f"Directory not empty, skipping: {dir_path}", 'WARNING')
except Exception as e:
self._log(f"Failed to remove directory {dir_path_str}: {e}", 'ERROR')
success = False
self._log(f"Removed {removed_count} directories")
return success
def remove_desktop_integration(self) -> bool:
"""Remove desktop integration"""
if not self.manifest.get('desktop_integration', False):
self._log("No desktop integration to remove")
return True
self._log("Removing desktop integration...")
try:
platform_system = self.manifest.get('platform', 'linux')
if platform_system == 'linux':
return self._remove_linux_desktop_integration()
elif platform_system == 'darwin':
return self._remove_macos_desktop_integration()
elif platform_system == 'windows':
return self._remove_windows_desktop_integration()
return True
except Exception as e:
self._log(f"Failed to remove desktop integration: {e}", 'ERROR')
return False
def _remove_linux_desktop_integration(self) -> bool:
"""Remove Linux desktop integration"""
success = True
# Remove desktop entry
desktop_file = Path.home() / '.local' / 'share' / 'applications' / 'laxyfile.desktop'
if desktop_file.exists():
try:
desktop_file.unlink()
self._log(f"Removed desktop entry: {desktop_file}")
except Exception as e:
self._log(f"Failed to remove desktop entry: {e}", 'ERROR')
success = False
# Remove MIME associations
mime_file = Path.home() / '.local' / 'share' / 'mime' / 'packages' / 'com.laxyfile.LaxyFile.xml'
if mime_file.exists():
try:
mime_file.unlink()
self._log(f"Removed MIME associations: {mime_file}")
except Exception as e:
self._log(f"Failed to remove MIME associations: {e}", 'ERROR')
success = False
# Remove icons
icon_sizes = ['16x16', '32x32', '48x48', '64x64', '128x128', '256x256']
for size in icon_sizes:
icon_file = Path.home() / '.local' / 'share' / 'icons' / 'hicolor' / size / 'apps' / 'com.laxyfile.LaxyFile.png'
if icon_file.exists():
try:
icon_file.unlink()
self._log(f"Removed icon: {icon_file}")
except Exception as e:
self._log(f"Failed to remove icon: {e}", 'WARNING')
# Update desktop database
try:
import subprocess
if shutil.which('update-desktop-database'):
subprocess.run([
'update-desktop-database',
str(Path.home() / '.local' / 'share' / 'applications')
], check=False)
if shutil.which('update-mime-database'):
subprocess.run([
'update-mime-database',
str(Path.home() / '.local' / 'share' / 'mime')
], check=False)
except Exception as e:
self._log(f"Failed to update desktop database: {e}", 'WARNING')
return success
def _remove_macos_desktop_integration(self) -> bool:
"""Remove macOS desktop integration"""
app_bundle = Path.home() / 'Applications' / 'LaxyFile.app'
if app_bundle.exists():
try:
shutil.rmtree(app_bundle)
self._log(f"Removed app bundle: {app_bundle}")
return True
except Exception as e:
self._log(f"Failed to remove app bundle: {e}", 'ERROR')
return False
return True
def _remove_windows_desktop_integration(self) -> bool:
"""Remove Windows desktop integration"""
success = True
# Remove Start Menu shortcut
start_menu_file = Path.home() / 'AppData' / 'Roaming' / 'Microsoft' / 'Windows' / 'Start Menu' / 'Programs' / 'LaxyFile.bat'
if start_menu_file.exists():
try:
start_menu_file.unlink()
self._log(f"Removed Start Menu shortcut: {start_menu_file}")
except Exception as e:
self._log(f"Failed to remove Start Menu shortcut: {e}", 'ERROR')
success = False
return success
def remove_python_package(self) -> bool:
"""Remove Python package installation"""
self._log("Removing Python package...")
try:
import subprocess
# Try to uninstall using pip
result = subprocess.run([
sys.executable, '-m', 'pip', 'uninstall', 'laxyfile', '-y'
], capture_output=True, text=True)
if result.returncode == 0:
self._log("Removed Python package")
return True
else:
self._log("Python package not found or already removed", 'WARNING')
return True
except Exception as e:
self._log(f"Failed to remove Python package: {e}", 'WARNING')
return True # Don't fail uninstallation for this
def remove_user_data(self, remove_config: bool = False) -> bool:
"""Remove user data and configuration"""
if not remove_config:
self._log("Keeping user configuration (use --remove-config to remove)")
return True
self._log("Removing user data and configuration...")
success = True
# Platform-specific config directories
platform_system = self.manifest.get('platform', 'linux')
if platform_system == 'windows':
config_dirs = [
Path.home() / 'AppData' / 'Roaming' / 'LaxyFile',
Path.home() / 'AppData' / 'Local' / 'LaxyFile'
]
elif platform_system == 'darwin':
config_dirs = [
Path.home() / 'Library' / 'Application Support' / 'LaxyFile',
Path.home() / 'Library' / 'Preferences' / 'com.laxyfile.LaxyFile.plist'
]
else: # Linux
config_dirs = [
Path.home() / '.config' / 'laxyfile',
Path.home() / '.local' / 'share' / 'laxyfile',
Path.home() / '.cache' / 'laxyfile'
]
for config_dir in config_dirs:
if config_dir.exists():
try:
if config_dir.is_file():
config_dir.unlink()
else:
shutil.rmtree(config_dir)
self._log(f"Removed user data: {config_dir}")
except Exception as e:
self._log(f"Failed to remove user data {config_dir}: {e}", 'ERROR')
success = False
return success
def uninstall(self, remove_config: bool = False) -> bool:
"""Run complete uninstallation"""
try:
self._log("Starting LaxyFile uninstallation...")
success = True
# Remove Python package
if not self.remove_python_package():
success = False
# Remove desktop integration
if not self.remove_desktop_integration():
success = False
# Remove installed files
if not self.remove_installed_files():
success = False
# Remove directories
if not self.remove_directories():
success = False
# Remove user data (optional)
if not self.remove_user_data(remove_config):
success = False
# Remove manifest file last
try:
if self.manifest_path.exists():
self.manifest_path.unlink()
self._log(f"Removed manifest: {self.manifest_path}")
except Exception as e:
self._log(f"Failed to remove manifest: {e}", 'WARNING')
if success:
self._log("Uninstallation completed successfully!")
else:
self._log("Uninstallation completed with some errors", 'WARNING')
return success
except Exception as e:
self._log(f"Uninstallation failed: {e}", 'ERROR')
return False
def find_installation_manifest() -> Path:
"""Find installation manifest automatically"""
# Common installation locations
search_paths = []
if sys.platform == 'win32':
search_paths = [
Path(os.environ.get('PROGRAMFILES', 'C:\\\\Program Files')) / 'LaxyFile' / 'install_manifest.json',
Path.home() / 'AppData' / 'Local' / 'LaxyFile' / 'install_manifest.json'
]
elif sys.platform == 'darwin':
search_paths = [
Path('/Applications/LaxyFile/install_manifest.json'),
Path.home() / 'Applications' / 'LaxyFile' / 'install_manifest.json'
]
else: # Linux
search_paths = [
Path('/opt/laxyfile/install_manifest.json'),
Path.home() / '.local' / 'share' / 'laxyfile' / 'install_manifest.json',
Path('/usr/local/share/laxyfile/install_manifest.json')
]
for path in search_paths:
if path.exists():
return path
return None
def main():
"""Main uninstaller entry point"""
parser = argparse.ArgumentParser(description='LaxyFile Uninstaller')
parser.add_argument(
'manifest',
nargs='?',
help='Path to installation manifest (auto-detected if not provided)'
)
parser.add_argument(
'--remove-config',
action='store_true',
help='Remove user configuration and data'
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='Verbose output'
)
args = parser.parse_args()
# Find manifest file
if args.manifest:
manifest_path = Path(args.manifest)
else:
manifest_path = find_installation_manifest()
if not manifest_path:
print("ERROR: Could not find installation manifest.")
print("Please provide the path to install_manifest.json")
sys.exit(1)
if not manifest_path.exists():
print(f"ERROR: Manifest file not found: {manifest_path}")
sys.exit(1)
try:
uninstaller = LaxyFileUninstaller(manifest_path, args.verbose)
# Confirm uninstallation
install_dir = uninstaller.manifest['install_dir']
print(f"This will remove LaxyFile from: {install_dir}")
if args.remove_config:
print("User configuration and data will also be removed.")
response = input("Continue? [y/N]: ").strip().lower()
if response not in ['y', 'yes']:
print("Uninstallation cancelled.")
sys.exit(0)
success = uninstaller.uninstall(args.remove_config)
sys.exit(0 if success else 1)
except Exception as e:
print(f"ERROR: {e}")
sys.exit(1)
if __name__ == '__main__':
main()