* Improve dev script for daemon * Ignore `.user` files created by Qt * Add `FORCE_DESKTOP_PROCESS` option * Catch errors related to getting profile dir * Disable IPC entirely if forced desktop * Use in-class init for AppConfig members * Refactor config to use safer memory management * Improve launch config to make OS-specific debugger usage clearer * Re-enable MainWindowTests on Windows, further refactor for memory safety, fixed some include orders * Remove dead singleton code * Swap order of tests * Use HTTPs for URLs * Fixed compile errors for `SYNERGY_ENABLE_LICENSING` compile path * Restore exec function call * Remove extra link in cancel dialog * Fixed broken link on activation cancel UI * Close dialog if activated * Fixed macOS enum ref to kCurrentProcess * Improve wording on cancel activation dialog * WIP - Test timeout (compile error) * Finished timeout logic (with smart pointer) * Include string_view * Switch to thread from jthread (maybe not supported by macOS compiler?) * Improve comment * Disable test on Windows * Add TODO related to jthread on macOS * Refactor settings and paths on Windows * Launch in desktop mode on Windows * Remove arg quote wraps which break desktop mode * Fixed qFatal on Linux * Remove test value * Follow original `AppConfig` accessor convention * Disable service checkbox if not Windows * Simplify TLS control enable logic * Update command and Git ignore * Fixed code style * Fixed include consistency * Fixed includes in validator * Fixed lint errors * Update ChangeLog * Use smart pointer for core process * Remove unneccesary default operators * Don't halt on stderr
90 lines
2.4 KiB
Python
90 lines
2.4 KiB
Python
import glob, os, shutil, sys
|
|
import lib.env as env
|
|
import lib.colors as colors
|
|
|
|
|
|
class CopyOptions:
|
|
def __init__(self, ignore_errors, verbose):
|
|
self.ignore_errors = ignore_errors
|
|
self.verbose = verbose
|
|
|
|
|
|
class CopyContext:
|
|
def __init__(self):
|
|
self.errors = 0
|
|
self.permission_error = False
|
|
|
|
|
|
def copy(source, target, options):
|
|
"""Copy files and directories from source to target."""
|
|
|
|
context = CopyContext()
|
|
if options.verbose:
|
|
print(f"Copying files from {source} to {target}")
|
|
|
|
try:
|
|
for match in glob.glob(source):
|
|
|
|
if os.path.isfile(match):
|
|
copy_file(match, target, options, context)
|
|
elif os.path.isdir(match):
|
|
copy_dir(match, target, options, context)
|
|
else:
|
|
raise RuntimeError(f"Path {match} is not a file or directory")
|
|
finally:
|
|
if context.errors and options.ignore_errors:
|
|
print(f"{colors.WARNING_TEXT} Ignored {context.errors} copy error(s)")
|
|
|
|
if context.permission_error and env.is_windows():
|
|
print(
|
|
f"{colors.HINT_TEXT} A Windows file permission error may mean that the file is in use"
|
|
)
|
|
|
|
|
|
def copy_dir(match, target, options, context):
|
|
if options.verbose:
|
|
print(f"Copying directory {match} to {target}")
|
|
|
|
try:
|
|
shutil.copytree(match, target, dirs_exist_ok=True)
|
|
except Exception as e:
|
|
handle_all_copy_errors(e, options, context)
|
|
|
|
|
|
def copy_file(match, target, options, context):
|
|
if options.verbose:
|
|
print(f"Copying file {match} to {target}")
|
|
|
|
try:
|
|
shutil.copy(match, target)
|
|
except Exception as e:
|
|
handle_all_copy_errors(e, options, context)
|
|
|
|
|
|
def handle_all_copy_errors(error, options, context):
|
|
|
|
if isinstance(error, shutil.Error):
|
|
for _, _, file_error in error.args[0]:
|
|
handle_copy_error(file_error, options, context)
|
|
else:
|
|
handle_copy_error(error, options, context)
|
|
|
|
if not options.ignore_errors:
|
|
raise error
|
|
|
|
|
|
def handle_copy_error(error, options, context):
|
|
if isinstance(error, PermissionError):
|
|
context.permission_error = True
|
|
|
|
if isinstance(error, str):
|
|
context.permission_error = error.startswith("[Errno 13] Permission denied")
|
|
|
|
context.errors += 1
|
|
|
|
if options.ignore_errors:
|
|
print(
|
|
f"{colors.WARNING_TEXT} Copy failed: {error}",
|
|
file=sys.stderr,
|
|
)
|