mirror of
https://github.com/deadc0de6/dotdrop.git
synced 2026-02-06 10:12:59 +00:00
151 lines
4.7 KiB
Python
151 lines
4.7 KiB
Python
"""
|
|
author: deadc0de6 (https://github.com/deadc0de6)
|
|
Copyright (c) 2023, deadc0de6
|
|
|
|
handle the un-installation of dotfiles
|
|
"""
|
|
|
|
import os
|
|
from dotdrop.logger import Logger
|
|
from dotdrop.utils import removepath, dir_empty
|
|
|
|
|
|
class Uninstaller:
|
|
"""dotfile uninstaller"""
|
|
|
|
def __init__(self, base='.', workdir='~/.config/dotdrop',
|
|
dry=False, safe=True, debug=False,
|
|
backup_suffix='.dotdropbak'):
|
|
"""
|
|
@base: directory path where to search for templates
|
|
@workdir: where to install template before symlinking
|
|
@dry: just simulate
|
|
@debug: enable debug
|
|
@backup_suffix: suffix for dotfile backup file
|
|
@safe: ask for any action
|
|
"""
|
|
base = os.path.expanduser(base)
|
|
base = os.path.normpath(base)
|
|
self.base = base
|
|
workdir = os.path.expanduser(workdir)
|
|
workdir = os.path.normpath(workdir)
|
|
self.workdir = workdir
|
|
self.dry = dry
|
|
self.safe = safe
|
|
self.debug = debug
|
|
self.backup_suffix = backup_suffix
|
|
self.log = Logger(debug=self.debug)
|
|
|
|
def uninstall(self, src, dst, linktype):
|
|
"""
|
|
uninstall dst
|
|
@src: dotfile source path in dotpath
|
|
@dst: dotfile destination path in the FS
|
|
@linktype: linktypes.LinkTypes
|
|
|
|
return
|
|
- True, None : success
|
|
- False, error_msg : error
|
|
"""
|
|
if not src or not dst:
|
|
self.log.dbg(f'cannot uninstall empty {src} or {dst}')
|
|
return True, None
|
|
|
|
# ensure exists
|
|
path = os.path.expanduser(dst)
|
|
path = os.path.normpath(path)
|
|
path = path.rstrip(os.sep)
|
|
|
|
if not os.path.isfile(path) and not os.path.isdir(path):
|
|
msg = f'cannot uninstall special file {path}'
|
|
return False, msg
|
|
|
|
if not os.path.exists(path):
|
|
self.log.dbg(f'cannot uninstall non existing {path}')
|
|
return True, None
|
|
|
|
msg = f'uninstalling \"{path}\" (link: {linktype})'
|
|
self.log.dbg(msg)
|
|
ret, msg = self._remove(path)
|
|
if ret:
|
|
if not self.dry:
|
|
self.log.sub(f'uninstall {dst}')
|
|
return ret, msg
|
|
|
|
def _descend(self, dirpath):
|
|
ret = True
|
|
self.log.dbg(f'recursively uninstall {dirpath}')
|
|
for sub in os.listdir(dirpath):
|
|
subpath = os.path.join(dirpath, sub)
|
|
if os.path.isdir(subpath):
|
|
self.log.dbg(f'under {dirpath} uninstall dir {subpath}')
|
|
self._descend(subpath)
|
|
else:
|
|
self.log.dbg(f'under {dirpath} uninstall file {subpath}')
|
|
subret, _ = self._remove(subpath)
|
|
if not subret:
|
|
ret = False
|
|
|
|
if dir_empty(dirpath):
|
|
# empty
|
|
self.log.dbg(f'remove empty dir {dirpath}')
|
|
if self.dry:
|
|
self.log.dry(f'would \"rm -r {dirpath}\"')
|
|
return True, ''
|
|
return self._remove_path(dirpath)
|
|
self.log.dbg(f'not removing non-empty dir {dirpath}')
|
|
return ret, ''
|
|
|
|
def _remove_path(self, path):
|
|
"""remove a file"""
|
|
try:
|
|
removepath(path)
|
|
except OSError as exc:
|
|
err = f'removing \"{path}\" failed: {exc}'
|
|
return False, err
|
|
return True, ''
|
|
|
|
def _remove(self, path):
|
|
"""remove path"""
|
|
self.log.dbg(f'handling uninstall of {path}')
|
|
if path.endswith(self.backup_suffix):
|
|
self.log.dbg(f'skip {path} ignored')
|
|
return True, ''
|
|
backup = f'{path}{self.backup_suffix}'
|
|
if os.path.exists(backup):
|
|
self.log.dbg(f'backup exists for {path}: {backup}')
|
|
return self._replace(path, backup)
|
|
self.log.dbg(f'no backup file for {path}')
|
|
|
|
if os.path.isdir(path):
|
|
self.log.dbg(f'{path} is a directory')
|
|
return self._descend(path)
|
|
|
|
if self.dry:
|
|
self.log.dry(f'would \"rm {path}\"')
|
|
return True, ''
|
|
|
|
msg = f'Remove {path}?'
|
|
if self.safe and not self.log.ask(msg):
|
|
return False, 'user refused'
|
|
self.log.dbg(f'removing {path}')
|
|
return self._remove_path(path)
|
|
|
|
def _replace(self, path, backup):
|
|
"""replace path by backup"""
|
|
if self.dry:
|
|
self.log.dry(f'would \"mv {backup} {path}\"')
|
|
return True, ''
|
|
|
|
msg = f'Restore {path} from {backup}?'
|
|
if self.safe and not self.log.ask(msg):
|
|
return False, 'user refused'
|
|
|
|
try:
|
|
self.log.dbg(f'mv {backup} {path}')
|
|
os.replace(backup, path)
|
|
except OSError as exc:
|
|
err = f'replacing \"{path}\" by \"{backup}\" failed: {exc}'
|
|
return False, err
|
|
return True, ''
|