kkpyutil 1.47.1__tar.gz → 1.49.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kkpyutil-1.47.1 → kkpyutil-1.49.0}/PKG-INFO +1 -1
- {kkpyutil-1.47.1 → kkpyutil-1.49.0}/kkpyutil.py +80 -14
- {kkpyutil-1.47.1 → kkpyutil-1.49.0}/pyproject.toml +1 -1
- {kkpyutil-1.47.1 → kkpyutil-1.49.0}/LICENSE +0 -0
- {kkpyutil-1.47.1 → kkpyutil-1.49.0}/README.md +0 -0
- {kkpyutil-1.47.1 → kkpyutil-1.49.0}/kkpyutil_helper/windows/kkttssave.ps1 +0 -0
- {kkpyutil-1.47.1 → kkpyutil-1.49.0}/kkpyutil_helper/windows/kkttsspeak.ps1 +0 -0
|
@@ -2079,28 +2079,75 @@ def move_file(src, dst, isdstdir=False):
|
|
|
2079
2079
|
return dst if not isdstdir else osp.join(dst, osp.basename(src))
|
|
2080
2080
|
|
|
2081
2081
|
|
|
2082
|
-
def sync_dirs(src_root, dst_root, logger=glogger):
|
|
2082
|
+
def sync_dirs(src_root, dst_root, logger=glogger, sudo=False):
|
|
2083
2083
|
"""
|
|
2084
2084
|
- assume src and dst folders are the same level of folder tree
|
|
2085
2085
|
- the result will be dst_root mirrors src_root
|
|
2086
2086
|
"""
|
|
2087
|
+
def _run_sudo_command(_cmd, password, _logger):
|
|
2088
|
+
sudo_cmd = ['sudo', '-S'] + _cmd
|
|
2089
|
+
try:
|
|
2090
|
+
process = subprocess.Popen(
|
|
2091
|
+
sudo_cmd,
|
|
2092
|
+
stdin=subprocess.PIPE,
|
|
2093
|
+
stdout=subprocess.PIPE,
|
|
2094
|
+
stderr=subprocess.PIPE,
|
|
2095
|
+
text=True
|
|
2096
|
+
)
|
|
2097
|
+
stdout, stderr = process.communicate(input=f'{password}\n')
|
|
2098
|
+
if process.returncode == 0:
|
|
2099
|
+
_logger.debug(f"Successfully executed: {' '.join(_cmd)}")
|
|
2100
|
+
return True
|
|
2101
|
+
else:
|
|
2102
|
+
_logger.error(f"Sudo command failed: {stderr}")
|
|
2103
|
+
return False
|
|
2104
|
+
except Exception as e:
|
|
2105
|
+
_logger.error(f"Failed to execute sudo command: {e}")
|
|
2106
|
+
return False
|
|
2087
2107
|
# Ensure the source directory exists
|
|
2088
2108
|
if not os.path.exists(src_root):
|
|
2089
2109
|
logger.error(f"Error: Source directory {src_root} does not exist.")
|
|
2090
2110
|
return False
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2111
|
+
if not sudo:
|
|
2112
|
+
# Iterate through the items inside the source directory
|
|
2113
|
+
for item in os.listdir(src_root):
|
|
2114
|
+
src_path = os.path.join(src_root, item)
|
|
2115
|
+
dst_path = os.path.join(dst_root, item)
|
|
2116
|
+
if os.path.isdir(src_path):
|
|
2117
|
+
# copytree with dirs_exist_ok=True will overwrite existing files
|
|
2118
|
+
# and merge directories without warning.
|
|
2119
|
+
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
|
|
2120
|
+
logger.info(f"Copied directory: {item}")
|
|
2121
|
+
else:
|
|
2122
|
+
# For individual files at the root of my_src_dir
|
|
2123
|
+
shutil.copy2(src_path, dst_path)
|
|
2124
|
+
logger.info(f"Copied file: {item}")
|
|
2125
|
+
return True
|
|
2126
|
+
# on macOS, merge to system folders
|
|
2127
|
+
# Identify the original user (the one who called sudo)
|
|
2128
|
+
# Default to current effective user if SUDO_USER isn't set
|
|
2129
|
+
user = os.environ.get('SUDO_USER') or os.environ.get('USER')
|
|
2130
|
+
if not user:
|
|
2131
|
+
logger.error("Could not determine the current user.")
|
|
2132
|
+
return False
|
|
2133
|
+
pwd = prompt_macos_admin_password()
|
|
2134
|
+
if pwd is None:
|
|
2135
|
+
logger.error('Failed to get admin password')
|
|
2136
|
+
return False
|
|
2137
|
+
# rsync flags:
|
|
2138
|
+
# -a: Archive mode (preserves links, etc.)
|
|
2139
|
+
# --chown: Forces the owner:group of all copied files
|
|
2140
|
+
# Note: 'staff' is the default group for users on macOS
|
|
2141
|
+
cmd = [
|
|
2142
|
+
'rsync', '-av', '--inplace',
|
|
2143
|
+
f'--chown={user}:staff',
|
|
2144
|
+
osp.join(src_root, ''), # Trailing slash copies contents
|
|
2145
|
+
dst_root
|
|
2146
|
+
]
|
|
2147
|
+
logger.info(f"Syncing to {dst_root} and setting owner to '{user}'...")
|
|
2148
|
+
if not _run_sudo_command(cmd, pwd, logger):
|
|
2149
|
+
logger.error(f'Sync failed with command: {cmd}; you may have entered wrong password')
|
|
2150
|
+
return False
|
|
2104
2151
|
return True
|
|
2105
2152
|
|
|
2106
2153
|
|
|
@@ -3163,6 +3210,25 @@ def json_from_text(json_str):
|
|
|
3163
3210
|
except json.JSONDecodeError as e:
|
|
3164
3211
|
return None, e
|
|
3165
3212
|
|
|
3213
|
+
|
|
3214
|
+
def prompt_macos_admin_password(action="Your operation"):
|
|
3215
|
+
applescript = f'''
|
|
3216
|
+
display dialog "{action} requires administrator privileges. Please enter your password:" default answer "" with hidden answer buttons {"Cancel", "OK"} default button "OK"
|
|
3217
|
+
text returned of the result
|
|
3218
|
+
'''
|
|
3219
|
+
try:
|
|
3220
|
+
result = subprocess.run(
|
|
3221
|
+
['osascript', '-e', applescript],
|
|
3222
|
+
capture_output=True,
|
|
3223
|
+
text=True,
|
|
3224
|
+
check=True
|
|
3225
|
+
)
|
|
3226
|
+
return result.stdout.strip()
|
|
3227
|
+
except subprocess.CalledProcessError:
|
|
3228
|
+
# User cancelled the dialog
|
|
3229
|
+
return None
|
|
3230
|
+
|
|
3231
|
+
|
|
3166
3232
|
# endregion
|
|
3167
3233
|
|
|
3168
3234
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|