atomicshop 2.15.11__py3-none-any.whl → 3.10.5__py3-none-any.whl
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.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -80
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +491 -182
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +1 -1
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
atomicshop/filesystem.py
CHANGED
|
@@ -9,12 +9,12 @@ from contextlib import contextmanager
|
|
|
9
9
|
from typing import Literal, Union
|
|
10
10
|
import tempfile
|
|
11
11
|
|
|
12
|
+
# noinspection PyPackageRequirements
|
|
12
13
|
import psutil
|
|
13
14
|
|
|
14
|
-
from .
|
|
15
|
-
from .basics import strings, list_of_dicts
|
|
15
|
+
from .basics import strings, list_of_dicts, list_of_classes
|
|
16
16
|
from .file_io import file_io
|
|
17
|
-
from . import hashing, datetimes
|
|
17
|
+
from . import hashing, datetimes, print_api
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
WINDOWS_DIRECTORY_SPECIAL_CHARACTERS = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
|
|
@@ -65,14 +65,18 @@ FILE_NAME_REPLACEMENT_DICT: dict = {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
# class TimeCouldNotBeFoundInFileNameError(Exception):
|
|
69
|
+
# pass
|
|
70
|
+
|
|
71
|
+
|
|
68
72
|
def get_home_directory(return_sudo_user: bool = False) -> str:
|
|
69
73
|
"""
|
|
70
74
|
Returns the home directory of the current user or the user who invoked sudo.
|
|
71
75
|
|
|
72
76
|
:param return_sudo_user: bool, if 'False', then the function will return the home directory of the user who invoked
|
|
73
77
|
sudo (if the script was invoked with sudo).
|
|
74
|
-
If 'True', then the function will return the home directory of the current user, doesn't matter if the script
|
|
75
|
-
invoked with sudo or not, if so home directory of the sudo user will be returned.
|
|
78
|
+
If 'True', then the function will return the home directory of the current user, doesn't matter if the script
|
|
79
|
+
was invoked with sudo or not, if so home directory of the sudo user will be returned.
|
|
76
80
|
"""
|
|
77
81
|
|
|
78
82
|
def return_home_directory_of_current_user():
|
|
@@ -209,16 +213,6 @@ def get_list_of_directories_in_file_path(
|
|
|
209
213
|
return directory_list
|
|
210
214
|
|
|
211
215
|
|
|
212
|
-
def get_file_name(file_path: str) -> str:
|
|
213
|
-
"""
|
|
214
|
-
The function will return file name of the file.
|
|
215
|
-
|
|
216
|
-
:param file_path: string, full file path.
|
|
217
|
-
:return: string.
|
|
218
|
-
"""
|
|
219
|
-
return str(Path(file_path).name)
|
|
220
|
-
|
|
221
|
-
|
|
222
216
|
def check_absolute_path(filesystem_path) -> bool:
|
|
223
217
|
"""
|
|
224
218
|
The function checks if the path provided is a full path (absolute) or relative.
|
|
@@ -240,21 +234,11 @@ def check_absolute_path___add_full(filesystem_path: str, full_path_to_add: str)
|
|
|
240
234
|
"""
|
|
241
235
|
|
|
242
236
|
if not check_absolute_path(filesystem_path):
|
|
243
|
-
return f'{full_path_to_add}{os.sep}{
|
|
237
|
+
return f'{full_path_to_add}{os.sep}{remove_last_separator(filesystem_path)}'
|
|
244
238
|
else:
|
|
245
239
|
return filesystem_path
|
|
246
240
|
|
|
247
241
|
|
|
248
|
-
def check_file_existence(file_path: str) -> bool:
|
|
249
|
-
"""This will be removed in future versions. Use 'is_file_exists' instead."""
|
|
250
|
-
return is_file_exists(file_path)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def check_directory_existence(directory_path: str) -> bool:
|
|
254
|
-
"""This will be removed in future versions. Use 'is_directory_exists' instead."""
|
|
255
|
-
return is_directory_exists(directory_path)
|
|
256
|
-
|
|
257
|
-
|
|
258
242
|
def is_file_exists(file_path: str) -> bool:
|
|
259
243
|
"""
|
|
260
244
|
Function to check if the path is a file.
|
|
@@ -295,16 +279,20 @@ def remove_file(file_path: str, **kwargs) -> bool:
|
|
|
295
279
|
|
|
296
280
|
try:
|
|
297
281
|
os.remove(file_path)
|
|
298
|
-
print_api(f'File Removed: {file_path}')
|
|
282
|
+
print_api.print_api(f'File Removed: {file_path}')
|
|
299
283
|
return True
|
|
300
284
|
# Since the file doesn't exist, we actually don't care, since we want to remove it anyway.
|
|
301
285
|
except FileNotFoundError:
|
|
302
286
|
message = f'File Removal Failed, File non-existent: {file_path}'
|
|
303
|
-
print_api(message, error_type=True, logger_method='critical', **kwargs)
|
|
287
|
+
print_api.print_api(message, error_type=True, logger_method='critical', **kwargs)
|
|
304
288
|
return False
|
|
305
289
|
|
|
306
290
|
|
|
307
|
-
def remove_directory(
|
|
291
|
+
def remove_directory(
|
|
292
|
+
directory_path: str,
|
|
293
|
+
force_readonly: bool = False,
|
|
294
|
+
print_kwargs: dict = None
|
|
295
|
+
) -> bool:
|
|
308
296
|
"""
|
|
309
297
|
Remove directory if it exists.
|
|
310
298
|
|
|
@@ -336,15 +324,69 @@ def remove_directory(directory_path: str, force_readonly: bool = False, print_kw
|
|
|
336
324
|
shutil.rmtree(directory_path, onerror=remove_readonly)
|
|
337
325
|
else:
|
|
338
326
|
shutil.rmtree(directory_path)
|
|
339
|
-
print_api(f'Directory Removed: {directory_path}', **print_kwargs)
|
|
327
|
+
print_api.print_api(f'Directory Removed: {directory_path}', **print_kwargs)
|
|
340
328
|
return True
|
|
341
329
|
# Since the directory doesn't exist, we actually don't care, since we want to remove it anyway.
|
|
342
330
|
except FileNotFoundError:
|
|
343
331
|
message = f'Directory Removal Failed, Directory non-existent: {directory_path}'
|
|
344
|
-
print_api(message, error_type=True, logger_method='critical', **print_kwargs)
|
|
332
|
+
print_api.print_api(message, error_type=True, logger_method='critical', **print_kwargs)
|
|
345
333
|
return False
|
|
346
334
|
|
|
347
335
|
|
|
336
|
+
def clear_directory(directory: str) -> tuple[list[str], list[str]]:
|
|
337
|
+
"""
|
|
338
|
+
The function will clear the directory of all files and subdirectories.
|
|
339
|
+
:param directory:
|
|
340
|
+
:return: tuple of lists of removed file paths and removed directory paths.
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
file_paths: list = []
|
|
344
|
+
directory_paths: list = []
|
|
345
|
+
# Iterate through all files and subdirectories in the directory
|
|
346
|
+
for item in os.listdir(directory):
|
|
347
|
+
item_path = os.path.join(directory, item)
|
|
348
|
+
# If it's a file, remove it
|
|
349
|
+
if os.path.isfile(item_path) or os.path.islink(item_path): # Handle symbolic links too
|
|
350
|
+
os.remove(item_path)
|
|
351
|
+
file_paths.append(item_path)
|
|
352
|
+
# If it's a directory, remove it and its contents
|
|
353
|
+
elif os.path.isdir(item_path):
|
|
354
|
+
shutil.rmtree(item_path)
|
|
355
|
+
directory_paths.append(item_path)
|
|
356
|
+
|
|
357
|
+
return file_paths, directory_paths
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def remove_empty_directories(directory_path: str) -> list[str]:
|
|
361
|
+
"""
|
|
362
|
+
Recursively removes empty directories in the given path, including the given path if it is empty.
|
|
363
|
+
|
|
364
|
+
:param directory_path: The starting directory path to check and remove empty directories.
|
|
365
|
+
"""
|
|
366
|
+
if not os.path.isdir(directory_path):
|
|
367
|
+
# print(f"Path '{directory_path}' is not a directory or does not exist.")
|
|
368
|
+
return []
|
|
369
|
+
|
|
370
|
+
removed_directories: list = []
|
|
371
|
+
# Iterate through the directory contents
|
|
372
|
+
for root, dirs, files in os.walk(directory_path, topdown=False):
|
|
373
|
+
for directory in dirs:
|
|
374
|
+
dir_path = os.path.join(root, directory)
|
|
375
|
+
# Check if the directory is empty
|
|
376
|
+
if not os.listdir(dir_path):
|
|
377
|
+
os.rmdir(dir_path)
|
|
378
|
+
removed_directories.append(dir_path)
|
|
379
|
+
# print(f"Removed empty directory: {dir_path}")
|
|
380
|
+
|
|
381
|
+
# Finally, check if the top-level directory is empty
|
|
382
|
+
if not os.listdir(directory_path):
|
|
383
|
+
os.rmdir(directory_path)
|
|
384
|
+
removed_directories.append(directory_path)
|
|
385
|
+
# print(f"Removed top-level empty directory: {path}")
|
|
386
|
+
|
|
387
|
+
return removed_directories
|
|
388
|
+
|
|
389
|
+
|
|
348
390
|
def create_directory(directory_fullpath: str):
|
|
349
391
|
# Create directory if non-existent.
|
|
350
392
|
# The library is used to create folder if it doesn't exist and won't raise exception if it does
|
|
@@ -353,41 +395,143 @@ def create_directory(directory_fullpath: str):
|
|
|
353
395
|
pathlib.Path(directory_fullpath).mkdir(parents=True, exist_ok=True)
|
|
354
396
|
|
|
355
397
|
|
|
356
|
-
def rename_file(
|
|
398
|
+
def rename_file(file_path: str, new_file_name: str) -> None:
|
|
357
399
|
"""
|
|
358
400
|
The function renames file from source to target.
|
|
359
401
|
|
|
360
|
-
:param
|
|
361
|
-
:param
|
|
402
|
+
:param file_path: string, full path to file that will be renamed.
|
|
403
|
+
:param new_file_name: string, new name of the file. No path should be included.
|
|
362
404
|
|
|
363
405
|
:return: None
|
|
364
406
|
"""
|
|
365
407
|
|
|
408
|
+
renamed_file_path = str(Path(file_path).parent) + os.sep + new_file_name
|
|
409
|
+
|
|
366
410
|
# Rename file.
|
|
367
|
-
os.rename(
|
|
411
|
+
os.rename(file_path, renamed_file_path)
|
|
368
412
|
|
|
369
413
|
|
|
370
|
-
|
|
371
|
-
|
|
414
|
+
def rename_file_with_special_characters(
|
|
415
|
+
file_path: str,
|
|
416
|
+
rename_dictionary: dict = None,
|
|
417
|
+
) -> str:
|
|
372
418
|
"""
|
|
373
|
-
The function will rename the file to
|
|
419
|
+
The function will rename the file to replace special characters from the file name with '_'.
|
|
420
|
+
If the file already exists, then the function will add a number to the end of the file name.
|
|
374
421
|
|
|
375
422
|
:param file_path: string, full path to file.
|
|
376
|
-
:param
|
|
377
|
-
|
|
423
|
+
:param rename_dictionary: dictionary, with special characters to replace.
|
|
424
|
+
If not specified, the default dictionary will be used: FILE_NAME_REPLACEMENT_DICT.
|
|
425
|
+
Example:
|
|
426
|
+
rename_dictionary = {
|
|
427
|
+
'$': '_',
|
|
428
|
+
' ': '_'
|
|
429
|
+
}
|
|
430
|
+
:return: string, full path to file with special characters renamed.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
if rename_dictionary is None:
|
|
434
|
+
rename_dictionary = FILE_NAME_REPLACEMENT_DICT
|
|
435
|
+
|
|
436
|
+
# Get the file name without extension
|
|
437
|
+
file_stem: str = str(Path(file_path).stem)
|
|
438
|
+
file_extension: str = str(Path(file_path).suffix)
|
|
439
|
+
|
|
440
|
+
# Remove special characters from the file name
|
|
441
|
+
new_file_stem = strings.replace_strings_with_values_from_dict(
|
|
442
|
+
string_to_replace=file_stem, dictionary=rename_dictionary)
|
|
443
|
+
|
|
444
|
+
# Rename the file
|
|
445
|
+
renamed_file_path = str(Path(file_path).parent) + os.sep + new_file_stem + file_extension
|
|
446
|
+
|
|
447
|
+
counter: int = 1
|
|
448
|
+
if os.path.isfile(renamed_file_path):
|
|
449
|
+
while True:
|
|
450
|
+
new_file_stem = f'{new_file_stem}_{counter}'
|
|
451
|
+
renamed_file_path = str(Path(file_path).parent) + os.sep + new_file_stem + file_extension
|
|
452
|
+
if not os.path.isfile(renamed_file_path):
|
|
453
|
+
break
|
|
454
|
+
counter += 1
|
|
455
|
+
|
|
456
|
+
os.rename(file_path, renamed_file_path)
|
|
457
|
+
|
|
458
|
+
return renamed_file_path
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def rename_files_and_directories_with_special_characters(
|
|
462
|
+
base_path: str,
|
|
463
|
+
rename_dictionary: dict = None
|
|
464
|
+
) -> None:
|
|
465
|
+
"""
|
|
466
|
+
Recursively renames all files and directories in the given directory to rename special characters.
|
|
378
467
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
468
|
+
:param base_path: str, the base directory to start processing.
|
|
469
|
+
:param rename_dictionary: dictionary, with special characters to replace.
|
|
470
|
+
If not specified, the default dictionary will be used: FILE_NAME_REPLACEMENT_DICT.
|
|
471
|
+
Example:
|
|
472
|
+
rename_dictionary = {
|
|
473
|
+
'$': '_',
|
|
474
|
+
' ': '_'
|
|
475
|
+
}
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
def sanitize_name(name: str) -> str:
|
|
479
|
+
nonlocal rename_dictionary
|
|
480
|
+
"""
|
|
481
|
+
Helper function to replace special characters in a string using a dictionary.
|
|
482
|
+
"""
|
|
483
|
+
for key, value in rename_dictionary.items():
|
|
484
|
+
name = name.replace(key, value)
|
|
485
|
+
return name
|
|
382
486
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
print(f"File is temporarily renamed to {temporary_file}")
|
|
386
|
-
# Perform operations with the temporarily named file here
|
|
487
|
+
if rename_dictionary is None:
|
|
488
|
+
rename_dictionary = FILE_NAME_REPLACEMENT_DICT
|
|
387
489
|
|
|
388
|
-
|
|
389
|
-
|
|
490
|
+
# Walk through the directory tree in reverse to ensure we rename files before directories
|
|
491
|
+
for root, dirs, files in os.walk(base_path, topdown=False):
|
|
492
|
+
# Rename files in the current directory
|
|
493
|
+
for file_name in files:
|
|
494
|
+
old_path = Path(root) / file_name
|
|
495
|
+
sanitized_name = sanitize_name(file_name)
|
|
496
|
+
new_path = Path(root) / sanitized_name
|
|
497
|
+
|
|
498
|
+
if sanitized_name != file_name: # Rename only if the name changed
|
|
499
|
+
# print(f"Renaming file: {old_path} -> {new_path}")
|
|
500
|
+
os.rename(old_path, new_path)
|
|
501
|
+
|
|
502
|
+
# Rename directories in the current directory
|
|
503
|
+
for dir_name in dirs:
|
|
504
|
+
old_path = Path(root) / dir_name
|
|
505
|
+
sanitized_name = sanitize_name(dir_name)
|
|
506
|
+
new_path = Path(root) / sanitized_name
|
|
507
|
+
|
|
508
|
+
if sanitized_name != dir_name: # Rename only if the name changed
|
|
509
|
+
# print(f"Renaming directory: {old_path} -> {new_path}")
|
|
510
|
+
os.rename(old_path, new_path)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
@contextmanager
|
|
514
|
+
def temporary_rename(file_path: str, temp_file_path) -> None:
|
|
515
|
+
# noinspection GrazieInspection
|
|
390
516
|
"""
|
|
517
|
+
The function will rename the file to temporary name and then rename it back to original name.
|
|
518
|
+
|
|
519
|
+
:param file_path: string, full path to file.
|
|
520
|
+
:param temp_file_path: string, temporary name to rename the file to.
|
|
521
|
+
:return: None.
|
|
522
|
+
|
|
523
|
+
Usage:
|
|
524
|
+
original_file = 'example.txt'
|
|
525
|
+
temporary_file = 'temp_example.txt'
|
|
526
|
+
|
|
527
|
+
with temporary_rename(original_file, temporary_file):
|
|
528
|
+
# Inside this block, the file exists as 'temp_example.txt'
|
|
529
|
+
print(f"File is temporarily renamed to {temporary_file}")
|
|
530
|
+
# Perform operations with the temporarily named file here
|
|
531
|
+
|
|
532
|
+
# Outside the block, it's back to 'example.txt'
|
|
533
|
+
print(f"File is renamed back to {original_file}")
|
|
534
|
+
"""
|
|
391
535
|
|
|
392
536
|
original_name = file_path
|
|
393
537
|
try:
|
|
@@ -436,17 +580,33 @@ def temporary_change_working_directory(new_working_directory: str) -> None:
|
|
|
436
580
|
os.chdir(original_working_directory)
|
|
437
581
|
|
|
438
582
|
|
|
439
|
-
def move_file(source_file_path: str,
|
|
583
|
+
def move_file(source_file_path: str, target_directory: str, overwrite: bool = True) -> None:
|
|
440
584
|
"""
|
|
441
585
|
The function moves file from source to target.
|
|
442
586
|
|
|
443
587
|
:param source_file_path: string, full path to source file.
|
|
444
|
-
:param
|
|
588
|
+
:param target_directory: string, full path to target directory.
|
|
445
589
|
:param overwrite: boolean, if 'False', then the function will not overwrite the file if it exists.
|
|
446
590
|
|
|
591
|
+
Example:
|
|
592
|
+
Before move:
|
|
593
|
+
Source file = 'C:/Users/user1/Downloads/file-to-move.txt'
|
|
594
|
+
Move to directory = 'C:/Users/user1/Documents'
|
|
595
|
+
|
|
596
|
+
Move:
|
|
597
|
+
source_file_path = 'C:/Users/user1/Downloads/file-to-move.txt'
|
|
598
|
+
target_directory = 'C:/Users/user1/Documents'
|
|
599
|
+
move_file(source_file_path, target_file_path)
|
|
600
|
+
|
|
601
|
+
After move:
|
|
602
|
+
'C:/Users/user1/Downloads'
|
|
603
|
+
'C:/Users/user1/Documents/file-to-move.txt'
|
|
604
|
+
|
|
447
605
|
:return: None
|
|
448
606
|
"""
|
|
449
607
|
|
|
608
|
+
target_file_path = target_directory + os.sep + Path(source_file_path).name
|
|
609
|
+
|
|
450
610
|
# Check if 'no_overwrite' is set to 'True' and if the file exists.
|
|
451
611
|
if not overwrite:
|
|
452
612
|
if is_file_exists(target_file_path):
|
|
@@ -469,12 +629,17 @@ def move_folder(source_directory: str, target_directory: str, overwrite: bool =
|
|
|
469
629
|
------------------------------
|
|
470
630
|
|
|
471
631
|
Example:
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
632
|
+
Before move:
|
|
633
|
+
Source folder = 'C:/Users/user1/Downloads/folder-to-move'
|
|
634
|
+
Move to directory = 'C:/Users/user1/Documents'
|
|
635
|
+
|
|
636
|
+
Move:
|
|
637
|
+
source_directory = 'C:/Users/user1/Downloads/folder-to-move'
|
|
638
|
+
target_directory = 'C:/Users/user1/Documents'
|
|
639
|
+
move_folder(source_directory, target_directory)
|
|
475
640
|
|
|
476
|
-
|
|
477
|
-
|
|
641
|
+
Result path of the 'folder-to-move' will be:
|
|
642
|
+
'C:/Users/user1/Documents/folder-to-move'
|
|
478
643
|
|
|
479
644
|
"""
|
|
480
645
|
|
|
@@ -487,14 +652,14 @@ def move_folder(source_directory: str, target_directory: str, overwrite: bool =
|
|
|
487
652
|
shutil.move(source_directory, target_directory)
|
|
488
653
|
|
|
489
654
|
|
|
490
|
-
def
|
|
655
|
+
def move_top_level_files_from_folder_to_folder(
|
|
491
656
|
source_directory: str,
|
|
492
657
|
target_directory: str,
|
|
493
658
|
overwrite: bool = True
|
|
494
659
|
):
|
|
495
660
|
"""
|
|
496
|
-
The function is
|
|
497
|
-
|
|
661
|
+
The function is non-recursive to move only top level files from source directory to target directory
|
|
662
|
+
overwriting existing files.
|
|
498
663
|
|
|
499
664
|
:param source_directory: string, full path to source directory.
|
|
500
665
|
:param target_directory: string, full path to target directory.
|
|
@@ -502,26 +667,50 @@ def move_files_from_folder_to_folder(
|
|
|
502
667
|
"""
|
|
503
668
|
|
|
504
669
|
# Iterate over each item in the source directory
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
source_item = os.path.join(source_directory, item)
|
|
508
|
-
destination_item = os.path.join(target_directory, item)
|
|
670
|
+
top_level_files: list[str] = get_paths_from_directory(
|
|
671
|
+
directory_path=source_directory, get_file=True, recursive=False, simple_list=True)
|
|
509
672
|
|
|
673
|
+
for source_item in top_level_files:
|
|
510
674
|
# Move each item to the destination directory
|
|
511
|
-
move_file(source_file_path=source_item,
|
|
675
|
+
move_file(source_file_path=source_item, target_directory=target_directory, overwrite=overwrite)
|
|
512
676
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
677
|
+
|
|
678
|
+
def move_folder_contents_to_folder(
|
|
679
|
+
source_directory: str,
|
|
680
|
+
target_directory: str,
|
|
681
|
+
overwrite: bool = True
|
|
682
|
+
):
|
|
683
|
+
"""
|
|
684
|
+
The function moves all the contents of the source directory to the target directory.
|
|
685
|
+
If target directory is inside the source directory, this folder will be skipped.
|
|
686
|
+
|
|
687
|
+
:param source_directory: string, full path to source directory.
|
|
688
|
+
:param target_directory: string, full path to target directory.
|
|
689
|
+
:param overwrite: boolean, if 'True', then the function will overwrite the files if they exist.
|
|
690
|
+
"""
|
|
691
|
+
|
|
692
|
+
# Make sure the destination directory exists, if not create it
|
|
693
|
+
os.makedirs(target_directory, exist_ok=True)
|
|
694
|
+
|
|
695
|
+
# Move contents of the source directory to the destination directory
|
|
696
|
+
for item in os.listdir(source_directory):
|
|
697
|
+
s = os.path.join(source_directory, item)
|
|
698
|
+
d = os.path.join(target_directory, item)
|
|
699
|
+
|
|
700
|
+
# if the target directory is inside the source directory, skip it
|
|
701
|
+
if os.path.abspath(target_directory).startswith(os.path.abspath(s)):
|
|
702
|
+
continue
|
|
703
|
+
|
|
704
|
+
if os.path.isdir(s):
|
|
705
|
+
if os.path.exists(d) and not overwrite:
|
|
706
|
+
raise FileExistsError(f"Directory already exists: {d}. Skipping due to overwrite=False.")
|
|
707
|
+
else:
|
|
708
|
+
shutil.move(s, d)
|
|
709
|
+
else:
|
|
710
|
+
if os.path.exists(d) and not overwrite:
|
|
711
|
+
raise FileExistsError(f"File {d} already exists. Skipping due to overwrite=False.")
|
|
712
|
+
else:
|
|
713
|
+
shutil.move(s, d)
|
|
525
714
|
|
|
526
715
|
|
|
527
716
|
def copy_file(
|
|
@@ -601,49 +790,101 @@ def copy_files_from_folder_to_folder(source_directory: str, target_directory: st
|
|
|
601
790
|
shutil.copy2(s, d)
|
|
602
791
|
|
|
603
792
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
793
|
+
class AtomicPath:
|
|
794
|
+
def __init__(self, path: str):
|
|
795
|
+
self.path: str = path
|
|
796
|
+
|
|
797
|
+
self.is_file: bool = os.path.isfile(path)
|
|
798
|
+
self.is_directory: bool = os.path.isdir(path)
|
|
799
|
+
self.name: str = Path(path).name
|
|
800
|
+
|
|
801
|
+
self.queried_directory: str = ''
|
|
802
|
+
# noinspection PyTypeChecker
|
|
803
|
+
self.last_modified: float = None
|
|
804
|
+
self.relative_dir: str = ''
|
|
805
|
+
self.binary: bytes = b''
|
|
806
|
+
self.hash: str = ''
|
|
807
|
+
self.datetime_datetime = None
|
|
808
|
+
self.datetime_string: str = ''
|
|
809
|
+
# noinspection PyTypeChecker
|
|
810
|
+
self.datetime_float: float = None
|
|
811
|
+
self.datetime_format: str = ''
|
|
812
|
+
|
|
813
|
+
def __str__(self):
|
|
814
|
+
return self.path
|
|
815
|
+
|
|
816
|
+
def update(
|
|
817
|
+
self,
|
|
818
|
+
path: str = None,
|
|
819
|
+
datetime_format: str = None,
|
|
820
|
+
update_datetime: bool = False,
|
|
821
|
+
update_last_modified: bool = False,
|
|
822
|
+
update_binary: bool = False,
|
|
823
|
+
update_hash: bool = False
|
|
824
|
+
):
|
|
825
|
+
if path:
|
|
826
|
+
if path != self.path:
|
|
827
|
+
self.queried_directory = ''
|
|
828
|
+
self.last_modified = None
|
|
829
|
+
self.relative_dir = ''
|
|
830
|
+
self.binary = b''
|
|
831
|
+
self.hash = ''
|
|
832
|
+
self.datetime_datetime = None
|
|
833
|
+
self.datetime_string = ''
|
|
834
|
+
self.datetime_float = None
|
|
835
|
+
self.datetime_format = ''
|
|
836
|
+
|
|
837
|
+
self.path = path
|
|
838
|
+
self.is_file = os.path.isfile(path)
|
|
839
|
+
self.is_directory = os.path.isdir(path)
|
|
840
|
+
self.name = Path(path).name
|
|
841
|
+
|
|
842
|
+
# Update the datetime format only if it is provided without the update_datetime boolean.
|
|
843
|
+
# Since, we don't want this variable if there is no relation between the datetime format and the filename.
|
|
844
|
+
# If the user want to put it manually, then we will not stop him, but this case is useless if filename
|
|
845
|
+
# doesn't contain the datetime.
|
|
846
|
+
if datetime_format and not update_datetime:
|
|
847
|
+
self.datetime_format = datetime_format
|
|
848
|
+
|
|
849
|
+
if update_datetime and not datetime_format and not self.datetime_format:
|
|
850
|
+
raise ValueError('If "update_datetime" is True, then "datetime_format" must be provided.')
|
|
851
|
+
|
|
852
|
+
if update_datetime:
|
|
853
|
+
self.datetime_datetime, self.datetime_string, self.datetime_float = (
|
|
854
|
+
datetimes.get_datetime_from_complex_string_by_pattern(self.name, datetime_format))
|
|
855
|
+
# If the provided datetime format is correct, then we will update the datetime format.
|
|
856
|
+
if self.datetime_string:
|
|
857
|
+
self.datetime_format = datetime_format
|
|
858
|
+
|
|
859
|
+
if update_last_modified:
|
|
860
|
+
self.last_modified = get_file_modified_time(self.path)
|
|
861
|
+
|
|
862
|
+
if update_binary:
|
|
863
|
+
self.binary = file_io.read_file(self.path, file_mode='rb', stdout=False)
|
|
864
|
+
|
|
865
|
+
if update_hash:
|
|
866
|
+
if self.binary:
|
|
867
|
+
self.hash = hashing.hash_bytes(self.binary)
|
|
868
|
+
else:
|
|
869
|
+
self.hash = hashing.hash_file(self.path)
|
|
636
870
|
|
|
637
871
|
|
|
638
|
-
def
|
|
872
|
+
def get_paths_from_directory(
|
|
639
873
|
directory_path: str,
|
|
874
|
+
simple_list: bool = False,
|
|
875
|
+
get_file: bool = False,
|
|
876
|
+
get_directory: bool = False,
|
|
640
877
|
recursive: bool = True,
|
|
641
878
|
file_name_check_pattern: str = '*',
|
|
879
|
+
datetime_format: str = None,
|
|
880
|
+
specific_date: str = None,
|
|
642
881
|
add_relative_directory: bool = False,
|
|
643
882
|
relative_file_name_as_directory: bool = False,
|
|
644
883
|
add_last_modified_time: bool = False,
|
|
645
|
-
sort_by_last_modified_time: bool = False
|
|
646
|
-
|
|
884
|
+
sort_by_last_modified_time: bool = False,
|
|
885
|
+
add_file_binary: bool = False,
|
|
886
|
+
add_file_hash: bool = False,
|
|
887
|
+
) -> Union[list[AtomicPath], list[str]]:
|
|
647
888
|
"""
|
|
648
889
|
Recursive, by option.
|
|
649
890
|
The function receives a filesystem directory as string, scans it recursively for files and returns list of
|
|
@@ -652,15 +893,23 @@ def get_file_paths_from_directory(
|
|
|
652
893
|
of that tuple.
|
|
653
894
|
|
|
654
895
|
:param directory_path: string to full path to directory on the filesystem to scan.
|
|
896
|
+
:param simple_list: boolean, if 'True', then the function will return only full file paths.
|
|
897
|
+
:param get_file: boolean, if 'True', then the function will return files.
|
|
898
|
+
:param get_directory: boolean, if 'True', then the function will return directories.
|
|
655
899
|
:param recursive: boolean.
|
|
656
900
|
'True', then the function will scan recursively in subdirectories.
|
|
657
901
|
'False', then the function will scan only in the directory that was passed.
|
|
658
902
|
:param file_name_check_pattern: string, if specified, the function will return only files that match the pattern.
|
|
659
903
|
The string can contain part of file name to check or full file name with extension.
|
|
660
904
|
Can contain wildcards.
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
905
|
+
:param datetime_format: datetime format string pattern to match the date in the file name.
|
|
906
|
+
If specified, the function will get the files by the date pattern.
|
|
907
|
+
|
|
908
|
+
Example:
|
|
909
|
+
datetime_format = '%Y-%m-%d'
|
|
910
|
+
:param specific_date: Specific date to get the file path.
|
|
911
|
+
If specified, the function will get the file by the specific date.
|
|
912
|
+
Meaning that 'datetime_format' must be specified.
|
|
664
913
|
:param add_relative_directory: boolean, if
|
|
665
914
|
'True', then the function will add relative directory to the output list.
|
|
666
915
|
In this case the output list will contain dictionaries with keys 'path' and 'relative_dir'.
|
|
@@ -672,47 +921,72 @@ def get_file_paths_from_directory(
|
|
|
672
921
|
to the output list.
|
|
673
922
|
:param sort_by_last_modified_time: boolean, if 'True', then the function will sort the output list by last
|
|
674
923
|
modified time of the file.
|
|
924
|
+
:param add_file_binary: boolean, if 'True', then the function will add binary content of the file to each file
|
|
925
|
+
object of the output list.
|
|
926
|
+
:param add_file_hash: boolean, if 'True', then the function will add hash of the file to each file object of the
|
|
927
|
+
output list.
|
|
675
928
|
|
|
676
929
|
:return: list of all found filenames with full file paths, list with relative folders to file excluding the
|
|
677
930
|
main folder.
|
|
678
931
|
"""
|
|
679
932
|
|
|
680
|
-
def
|
|
933
|
+
def get_path(file_or_directory: str):
|
|
681
934
|
"""
|
|
682
935
|
Function gets the full file path, adds it to the found 'object_list' and gets the relative path to that
|
|
683
936
|
file, against the main path to directory that was passed to the parent function.
|
|
684
937
|
"""
|
|
685
938
|
|
|
686
|
-
|
|
939
|
+
if strings.match_pattern_against_string(file_name_check_pattern, file_or_directory):
|
|
940
|
+
file_or_dir_path: str = os.path.join(dir_path, file_or_directory)
|
|
687
941
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
file_result: dict = dict()
|
|
942
|
+
if simple_list:
|
|
943
|
+
object_list.append(file_or_dir_path)
|
|
944
|
+
return
|
|
692
945
|
|
|
693
|
-
|
|
694
|
-
|
|
946
|
+
path_object: AtomicPath = AtomicPath(path=file_or_dir_path)
|
|
947
|
+
path_object.queried_directory = directory_path
|
|
695
948
|
|
|
696
949
|
if add_relative_directory:
|
|
697
950
|
# if 'relative_file_name_as_directory' was passed.
|
|
698
951
|
if relative_file_name_as_directory:
|
|
699
952
|
# Output the path with filename.
|
|
700
|
-
|
|
701
|
-
directory_path,
|
|
953
|
+
path_object.relative_dir = _get_relative_output_path_from_input_path(
|
|
954
|
+
directory_path, dir_path, file_or_directory)
|
|
702
955
|
# if 'relative_file_name_as_directory' wasn't passed.
|
|
703
956
|
else:
|
|
704
957
|
# Output the path without filename.
|
|
705
|
-
|
|
958
|
+
path_object.relative_dir = _get_relative_output_path_from_input_path(
|
|
959
|
+
directory_path, dir_path)
|
|
706
960
|
|
|
707
961
|
# Remove separator from the beginning if exists.
|
|
708
|
-
|
|
962
|
+
path_object.relative_dir = path_object.relative_dir.removeprefix(os.sep)
|
|
709
963
|
|
|
710
964
|
# If 'add_last_modified_time' was passed.
|
|
711
965
|
if add_last_modified_time:
|
|
712
966
|
# Get last modified time of the file.
|
|
713
|
-
|
|
967
|
+
path_object.update(update_last_modified=True)
|
|
968
|
+
|
|
969
|
+
if datetime_format:
|
|
970
|
+
# Get the datetime object from the file name by the date format pattern.
|
|
971
|
+
path_object.update(datetime_format=datetime_format, update_datetime=True)
|
|
972
|
+
# If the datetime string is empty, then the file doesn't contain the date in the filename.
|
|
973
|
+
if not path_object.datetime_string:
|
|
974
|
+
return
|
|
975
|
+
|
|
976
|
+
if specific_date:
|
|
977
|
+
if path_object.datetime_string != specific_date:
|
|
978
|
+
return
|
|
714
979
|
|
|
715
|
-
|
|
980
|
+
object_list.append(path_object)
|
|
981
|
+
|
|
982
|
+
if get_file and get_directory:
|
|
983
|
+
raise ValueError('Parameters "get_file" and "get_directory" cannot be both "True".')
|
|
984
|
+
elif not get_file and not get_directory:
|
|
985
|
+
raise ValueError('Parameters "get_file" and "get_directory" cannot be both "False".')
|
|
986
|
+
|
|
987
|
+
if get_directory and (add_file_binary or add_file_hash):
|
|
988
|
+
raise ValueError(
|
|
989
|
+
'While "get_directory" is True, Parameters "add_file_binary" or "add_file_hash" cannot be "True".')
|
|
716
990
|
|
|
717
991
|
if sort_by_last_modified_time and not add_last_modified_time:
|
|
718
992
|
raise ValueError('Parameter "sort_by_last_modified_time" cannot be "True" if parameter '
|
|
@@ -721,18 +995,25 @@ def get_file_paths_from_directory(
|
|
|
721
995
|
raise ValueError('Parameter "relative_file_name_as_directory" cannot be "True" if parameter '
|
|
722
996
|
'"add_relative_directory" is not "True".')
|
|
723
997
|
|
|
998
|
+
if not datetime_format and specific_date:
|
|
999
|
+
raise ValueError('If "specific_date" is specified, "datetime_format" must be specified.')
|
|
1000
|
+
|
|
724
1001
|
# === Function main ================
|
|
725
1002
|
# Define locals.
|
|
726
1003
|
object_list: list = list()
|
|
727
1004
|
|
|
728
1005
|
# "Walk" over all the directories and subdirectories - make list of full file paths inside the directory
|
|
729
1006
|
# recursively.
|
|
730
|
-
for
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1007
|
+
for dir_path, sub_dirs, files in os.walk(directory_path):
|
|
1008
|
+
if get_file:
|
|
1009
|
+
# Iterate through all the file names that were found in the folder.
|
|
1010
|
+
for path in files:
|
|
1011
|
+
# If 'file_name_check_pattern' was passed.
|
|
1012
|
+
get_path(path)
|
|
1013
|
+
elif get_directory:
|
|
1014
|
+
# Iterate through all the directory names that were found in the folder.
|
|
1015
|
+
for path in sub_dirs:
|
|
1016
|
+
get_path(path)
|
|
736
1017
|
|
|
737
1018
|
if not recursive:
|
|
738
1019
|
break
|
|
@@ -740,7 +1021,34 @@ def get_file_paths_from_directory(
|
|
|
740
1021
|
# If 'sort_by_last_modified_time' was passed.
|
|
741
1022
|
if sort_by_last_modified_time:
|
|
742
1023
|
# Sort the list by last modified time.
|
|
743
|
-
object_list =
|
|
1024
|
+
object_list = list_of_classes.sort_by_attributes(object_list, attribute_list=['last_modified'])
|
|
1025
|
+
|
|
1026
|
+
if add_file_binary or add_file_hash:
|
|
1027
|
+
if add_file_binary and not add_file_hash:
|
|
1028
|
+
prefix_string = 'Reading Binary of File: '
|
|
1029
|
+
elif add_file_hash and not add_file_binary:
|
|
1030
|
+
prefix_string = 'Reading Hash of File: '
|
|
1031
|
+
elif add_file_binary and add_file_hash:
|
|
1032
|
+
prefix_string = 'Reading Binary and Hash of File: '
|
|
1033
|
+
else:
|
|
1034
|
+
prefix_string = 'Reading File: '
|
|
1035
|
+
|
|
1036
|
+
for file_index, file_path in enumerate(object_list):
|
|
1037
|
+
print_api.print_status_of_list(
|
|
1038
|
+
list_instance=object_list, prefix_string=prefix_string, current_state=(file_index + 1))
|
|
1039
|
+
|
|
1040
|
+
# If 'add_binary' was passed.
|
|
1041
|
+
if add_file_binary and file_path.is_file:
|
|
1042
|
+
# Get binary content of the file.
|
|
1043
|
+
object_list[file_index].binary = file_io.read_file(file_path.path, file_mode='rb', stdout=False)
|
|
1044
|
+
|
|
1045
|
+
# If 'add_file_hash' was passed.
|
|
1046
|
+
if add_file_hash and file_path.is_file:
|
|
1047
|
+
# Get hash of the file.
|
|
1048
|
+
if file_path.binary:
|
|
1049
|
+
object_list[file_index].hash = hashing.hash_bytes(file_path.binary)
|
|
1050
|
+
else:
|
|
1051
|
+
object_list[file_index].hash = hashing.hash_file(file_path.path)
|
|
744
1052
|
|
|
745
1053
|
return object_list
|
|
746
1054
|
|
|
@@ -815,17 +1123,6 @@ def remove_last_separator(directory_path: str) -> str:
|
|
|
815
1123
|
return directory_path.removesuffix(os.sep)
|
|
816
1124
|
|
|
817
1125
|
|
|
818
|
-
def remove_first_separator(filesystem_path: str) -> str:
|
|
819
|
-
"""
|
|
820
|
-
The function removes the first character in 'filesystem_path' if it is a separator returning the processed string.
|
|
821
|
-
If the first character is not a separator, nothing is happening.
|
|
822
|
-
|
|
823
|
-
:param filesystem_path:
|
|
824
|
-
:return:
|
|
825
|
-
"""
|
|
826
|
-
return filesystem_path.removesuffix(os.sep)
|
|
827
|
-
|
|
828
|
-
|
|
829
1126
|
def add_last_separator(filesystem_path: str) -> str:
|
|
830
1127
|
"""
|
|
831
1128
|
The function adds a separator to the end of the path if it doesn't exist.
|
|
@@ -861,14 +1158,14 @@ def get_files_and_folders(directory_path: str, string_contains: str = str()):
|
|
|
861
1158
|
return files_folders_list
|
|
862
1159
|
|
|
863
1160
|
|
|
864
|
-
def get_file_modified_time(
|
|
1161
|
+
def get_file_modified_time(file_or_dir_path: str) -> float:
|
|
865
1162
|
"""
|
|
866
1163
|
The function returns the time of last modification of the file in seconds since the epoch.
|
|
867
1164
|
|
|
868
|
-
:param
|
|
1165
|
+
:param file_or_dir_path: string, full path to file or directory.
|
|
869
1166
|
:return: float, time of last modification of the file in seconds since the epoch.
|
|
870
1167
|
"""
|
|
871
|
-
return os.path.getmtime(
|
|
1168
|
+
return os.path.getmtime(file_or_dir_path)
|
|
872
1169
|
|
|
873
1170
|
|
|
874
1171
|
def change_last_modified_date_of_file(file_path: str, new_date: float) -> None:
|
|
@@ -890,43 +1187,6 @@ def change_last_modified_date_of_file(file_path: str, new_date: float) -> None:
|
|
|
890
1187
|
os.utime(file_path, (new_date, new_date))
|
|
891
1188
|
|
|
892
1189
|
|
|
893
|
-
def get_file_hashes_from_directory(directory_path: str, recursive: bool = False, add_binary: bool = False) -> list:
|
|
894
|
-
"""
|
|
895
|
-
The function scans a directory for files and returns a list of dictionaries with file path and hash of the file.
|
|
896
|
-
Binary option can be specified.
|
|
897
|
-
|
|
898
|
-
:param directory_path: string, of full path to directory you want to return file names of.
|
|
899
|
-
:param recursive: boolean.
|
|
900
|
-
'True', then the function will scan recursively in subdirectories.
|
|
901
|
-
'False', then the function will scan only in the directory that was passed.
|
|
902
|
-
:param add_binary: boolean, if 'True', then the function will add the binary of the file to the output list.
|
|
903
|
-
|
|
904
|
-
:return: list of dicts with full file paths, hashes and binaries (if specified).
|
|
905
|
-
"""
|
|
906
|
-
|
|
907
|
-
# Get all the files.
|
|
908
|
-
file_paths_list = get_file_paths_from_directory(directory_path, recursive=recursive)
|
|
909
|
-
|
|
910
|
-
# Create a list of dictionaries, each dictionary is a file with its hash.
|
|
911
|
-
files: list = list()
|
|
912
|
-
for file_index, file_path in enumerate(file_paths_list):
|
|
913
|
-
print_status_of_list(
|
|
914
|
-
list_instance=file_paths_list, prefix_string=f'Reading File: ', current_state=(file_index + 1))
|
|
915
|
-
|
|
916
|
-
file_info: dict = dict()
|
|
917
|
-
file_info['path'] = file_path['path']
|
|
918
|
-
|
|
919
|
-
if add_binary:
|
|
920
|
-
file_info['binary'] = file_io.read_file(file_path['path'], file_mode='rb', stdout=False)
|
|
921
|
-
file_info['hash'] = hashing.hash_bytes(file_info['binary'])
|
|
922
|
-
else:
|
|
923
|
-
file_info['hash'] = hashing.hash_file(file_path['path'])
|
|
924
|
-
|
|
925
|
-
files.append(file_info)
|
|
926
|
-
|
|
927
|
-
return files
|
|
928
|
-
|
|
929
|
-
|
|
930
1190
|
def find_duplicates_by_hash(
|
|
931
1191
|
directory_path: str,
|
|
932
1192
|
recursive: bool = False,
|
|
@@ -945,33 +1205,34 @@ def find_duplicates_by_hash(
|
|
|
945
1205
|
"""
|
|
946
1206
|
|
|
947
1207
|
# Get all the files.
|
|
948
|
-
files: list =
|
|
1208
|
+
files: list = get_paths_from_directory(
|
|
1209
|
+
directory_path, get_file=True, recursive=recursive, add_file_binary=add_binary)
|
|
949
1210
|
|
|
950
1211
|
same_hash_files: list = list()
|
|
951
1212
|
# Check if there are files that have exactly the same hash.
|
|
952
|
-
for
|
|
1213
|
+
for atomic_path in files:
|
|
953
1214
|
# Create a list of files that have the same hash for current 'firmware'.
|
|
954
1215
|
current_run_list: list = list()
|
|
955
|
-
for
|
|
1216
|
+
for atomic_path_compare in files:
|
|
956
1217
|
# Add all the 'firmware_compare' that have the same hash to the list.
|
|
957
|
-
if (
|
|
958
|
-
|
|
1218
|
+
if (atomic_path.hash == atomic_path_compare.hash and
|
|
1219
|
+
atomic_path.path != atomic_path_compare.path):
|
|
959
1220
|
# Check if current 'firmware' is already in the 'same_hash_files' list. If not, add 'firmware_compare'
|
|
960
1221
|
# to the 'current_run_list'.
|
|
961
1222
|
if not any(list_of_dicts.is_value_exist_in_key(
|
|
962
|
-
list_of_dicts=test_hash, key='path', value_to_match=
|
|
1223
|
+
list_of_dicts=test_hash, key='path', value_to_match=atomic_path.path) for
|
|
963
1224
|
test_hash in same_hash_files):
|
|
964
1225
|
current_run_list.append({
|
|
965
|
-
'path':
|
|
966
|
-
'hash':
|
|
1226
|
+
'path': atomic_path_compare.path,
|
|
1227
|
+
'hash': atomic_path_compare.hash
|
|
967
1228
|
})
|
|
968
1229
|
|
|
969
1230
|
if current_run_list:
|
|
970
1231
|
# After the iteration of the 'firmware_compare' finished and the list is not empty, add the 'firmware'
|
|
971
1232
|
# to the list.
|
|
972
1233
|
current_run_list.append({
|
|
973
|
-
'path':
|
|
974
|
-
'hash':
|
|
1234
|
+
'path': atomic_path.path,
|
|
1235
|
+
'hash': atomic_path.hash
|
|
975
1236
|
})
|
|
976
1237
|
same_hash_files.append(current_run_list)
|
|
977
1238
|
|
|
@@ -1052,39 +1313,40 @@ def get_directory_size(directory_path: str):
|
|
|
1052
1313
|
|
|
1053
1314
|
|
|
1054
1315
|
def get_subpaths_between(start_path: str, end_path: str) -> list[str]:
|
|
1316
|
+
# noinspection GrazieInspection
|
|
1055
1317
|
"""
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1318
|
+
Get the subpaths between two paths.
|
|
1319
|
+
:param start_path: string, start path.
|
|
1320
|
+
:param end_path: string, end path.
|
|
1321
|
+
:return:
|
|
1060
1322
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1323
|
+
Example Linux:
|
|
1324
|
+
start_path = '/test/1'
|
|
1325
|
+
end_path = '/test/1/2/3/4'
|
|
1064
1326
|
|
|
1065
|
-
|
|
1327
|
+
subpaths = get_subpaths_between(start_path, end_path)
|
|
1066
1328
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1329
|
+
subpaths = [
|
|
1330
|
+
'/test/1'
|
|
1331
|
+
'/test/1/2',
|
|
1332
|
+
'/test/1/2/3',
|
|
1333
|
+
'/test/1/2/3/4',
|
|
1334
|
+
]
|
|
1073
1335
|
|
|
1074
1336
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1337
|
+
Example Windows:
|
|
1338
|
+
start_path = 'C:\\test\\1'
|
|
1339
|
+
end_path = 'C:\\test\\1\\2\\3\\4'
|
|
1078
1340
|
|
|
1079
|
-
|
|
1341
|
+
subpaths = get_subpaths_between(start_path, end_path)
|
|
1080
1342
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1343
|
+
subpaths = [
|
|
1344
|
+
'C:\\test\\1',
|
|
1345
|
+
'C:\\test\\1\\2',
|
|
1346
|
+
'C:\\test\\1\\2\\3',
|
|
1347
|
+
'C:\\test\\1\\2\\3\\4',
|
|
1348
|
+
]
|
|
1349
|
+
"""
|
|
1088
1350
|
|
|
1089
1351
|
# Detect slash type based on the input (default to forward slash)
|
|
1090
1352
|
slash_type = "\\" if "\\" in start_path else "/"
|
|
@@ -1125,13 +1387,13 @@ def get_subpaths_between(start_path: str, end_path: str) -> list[str]:
|
|
|
1125
1387
|
# else:
|
|
1126
1388
|
# raise ValueError("Start path must be a parent of the end path")
|
|
1127
1389
|
#
|
|
1128
|
-
# # Reverse the list so it goes from start to end.
|
|
1390
|
+
# # Reverse the list, so it goes from start to end.
|
|
1129
1391
|
# subpaths.reverse()
|
|
1130
1392
|
#
|
|
1131
1393
|
# return subpaths
|
|
1132
1394
|
|
|
1133
1395
|
|
|
1134
|
-
def create_dict_of_paths_list(list_of_paths: list) ->
|
|
1396
|
+
def create_dict_of_paths_list(list_of_paths: list) -> list:
|
|
1135
1397
|
"""
|
|
1136
1398
|
The function receives a list of paths and returns a dictionary with keys as the paths and values as the
|
|
1137
1399
|
subpaths of the key path.
|
|
@@ -1167,7 +1429,7 @@ def create_dict_of_paths_list(list_of_paths: list) -> dict:
|
|
|
1167
1429
|
:return: dictionary.
|
|
1168
1430
|
"""
|
|
1169
1431
|
|
|
1170
|
-
structure = []
|
|
1432
|
+
structure: list = []
|
|
1171
1433
|
for path in list_of_paths:
|
|
1172
1434
|
create_dict_of_path(path, structure)
|
|
1173
1435
|
return structure
|
|
@@ -1175,70 +1437,20 @@ def create_dict_of_paths_list(list_of_paths: list) -> dict:
|
|
|
1175
1437
|
|
|
1176
1438
|
def create_dict_of_path(
|
|
1177
1439
|
path: str,
|
|
1178
|
-
# structure_dict: dict,
|
|
1179
1440
|
structure_list: list,
|
|
1180
|
-
add_data_to_entry: any = None
|
|
1181
|
-
add_data_key: str = 'addon',
|
|
1182
|
-
parent_entry: str = None
|
|
1441
|
+
add_data_to_entry: list[dict[str, any]] = None
|
|
1183
1442
|
):
|
|
1184
1443
|
"""
|
|
1185
1444
|
The function receives a path and a list, and adds the path to the list.
|
|
1186
|
-
|
|
1187
1445
|
Check the working example from 'create_dict_of_paths_list' function.
|
|
1188
1446
|
|
|
1189
1447
|
:param path: string, path.
|
|
1190
1448
|
:param structure_list: list to add the path to.
|
|
1191
|
-
:param add_data_to_entry:
|
|
1192
|
-
|
|
1193
|
-
:param parent_entry: string, for internal use to pass the current parent entry.
|
|
1449
|
+
:param add_data_to_entry: a list of dicts with data to add to the entry.
|
|
1450
|
+
dict format: {key: data}
|
|
1194
1451
|
:return:
|
|
1195
1452
|
"""
|
|
1196
1453
|
|
|
1197
|
-
# # Normalize path for cross-platform compatibility
|
|
1198
|
-
# normalized_path = path.replace("\\", "/")
|
|
1199
|
-
# parts = normalized_path.strip("/").split("/")
|
|
1200
|
-
# current_level = structure_dict
|
|
1201
|
-
#
|
|
1202
|
-
# for part in parts[:-1]: # Iterate through the directories
|
|
1203
|
-
# # If the part is not already a key in the current level of the structure, add it
|
|
1204
|
-
# if part not in current_level:
|
|
1205
|
-
# current_level[part] = {}
|
|
1206
|
-
# current_level = current_level[part]
|
|
1207
|
-
#
|
|
1208
|
-
# # Create the entry for the file with additional data
|
|
1209
|
-
# file_entry = {"entry": parts[-1], add_data_key: add_data_to_entry}
|
|
1210
|
-
#
|
|
1211
|
-
# # We're adding file entries under numeric keys.
|
|
1212
|
-
# if isinstance(current_level, dict) and all(isinstance(key, int) for key in current_level.keys()):
|
|
1213
|
-
# current_level[len(current_level)] = file_entry
|
|
1214
|
-
# else:
|
|
1215
|
-
# # Handle cases where there's a mix of numeric keys and directory names
|
|
1216
|
-
# # Find the next available numeric key
|
|
1217
|
-
# next_key = max([key if isinstance(key, int) else -1 for key in current_level.keys()], default=-1) + 1
|
|
1218
|
-
# current_level[next_key] = file_entry
|
|
1219
|
-
|
|
1220
|
-
# entries_key_name = "__entries__"
|
|
1221
|
-
#
|
|
1222
|
-
# # Normalize path for cross-platform compatibility
|
|
1223
|
-
# normalized_path = path.replace("\\", "/")
|
|
1224
|
-
# parts = normalized_path.strip("/").split("/")
|
|
1225
|
-
# current_level = structure_dict
|
|
1226
|
-
#
|
|
1227
|
-
# for part in parts[:-1]: # Navigate through or create directory structure
|
|
1228
|
-
# if part not in current_level:
|
|
1229
|
-
# current_level[part] = {}
|
|
1230
|
-
# current_level = current_level[part]
|
|
1231
|
-
#
|
|
1232
|
-
# # Create the entry for the file with additional data
|
|
1233
|
-
# file_entry = {"entry": parts[-1], add_data_key: add_data_to_entry}
|
|
1234
|
-
#
|
|
1235
|
-
# # If the current level (final directory) does not have an "entries" key for files, create it
|
|
1236
|
-
# if entries_key_name not in current_level:
|
|
1237
|
-
# current_level[entries_key_name] = []
|
|
1238
|
-
#
|
|
1239
|
-
# # Append the file entry to the list associated with the "entries" key
|
|
1240
|
-
# current_level[entries_key_name].append(file_entry)
|
|
1241
|
-
|
|
1242
1454
|
# Normalize path for cross-platform compatibility
|
|
1243
1455
|
normalized_path = path.replace("\\", "/")
|
|
1244
1456
|
parts = normalized_path.strip("/").split("/")
|
|
@@ -1246,28 +1458,36 @@ def create_dict_of_path(
|
|
|
1246
1458
|
current_level = structure_list
|
|
1247
1459
|
|
|
1248
1460
|
for i, part in enumerate(parts):
|
|
1249
|
-
# Determine if this is the last part (a file)
|
|
1461
|
+
# Determine if this is the last part (a file or final component of the path)
|
|
1250
1462
|
is_last_part = (i == len(parts) - 1)
|
|
1251
1463
|
|
|
1252
1464
|
# Try to find an existing entry for this part
|
|
1253
1465
|
existing_entry = next((item for item in current_level if item["entry"] == part), None)
|
|
1254
1466
|
|
|
1255
1467
|
if existing_entry is None:
|
|
1256
|
-
#
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1468
|
+
# Create a new entry
|
|
1469
|
+
new_entry = {"entry": part, "included": []}
|
|
1470
|
+
|
|
1471
|
+
# Add additional data if it's the last part
|
|
1472
|
+
if is_last_part and add_data_to_entry:
|
|
1473
|
+
for data_dict in add_data_to_entry:
|
|
1474
|
+
new_entry.update(data_dict)
|
|
1261
1475
|
|
|
1262
1476
|
current_level.append(new_entry)
|
|
1477
|
+
|
|
1263
1478
|
# Only update current_level if it's not the last part
|
|
1264
1479
|
if not is_last_part:
|
|
1265
1480
|
current_level = new_entry["included"]
|
|
1266
1481
|
else:
|
|
1267
|
-
# If it's not the last part
|
|
1482
|
+
# If the entry exists and it's not the last part, navigate deeper
|
|
1268
1483
|
if not is_last_part:
|
|
1269
1484
|
current_level = existing_entry["included"]
|
|
1270
1485
|
|
|
1486
|
+
# If the entry exists and it's the last part, update with additional data
|
|
1487
|
+
if is_last_part and add_data_to_entry:
|
|
1488
|
+
for data_dict in add_data_to_entry:
|
|
1489
|
+
existing_entry.update(data_dict)
|
|
1490
|
+
|
|
1271
1491
|
|
|
1272
1492
|
def list_open_files_in_directory(directory):
|
|
1273
1493
|
"""
|
|
@@ -1284,6 +1504,7 @@ def list_open_files_in_directory(directory):
|
|
|
1284
1504
|
proc_open_files = proc.open_files()
|
|
1285
1505
|
for file in proc_open_files:
|
|
1286
1506
|
if file.path.startswith(directory):
|
|
1507
|
+
# noinspection PyUnresolvedReferences
|
|
1287
1508
|
open_files.append((proc.info['pid'], proc.info['name'], file.path))
|
|
1288
1509
|
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
|
1289
1510
|
# Ignore processes that can't be accessed
|
|
@@ -1362,7 +1583,13 @@ def is_file_open_by_process(file_path: str) -> bool:
|
|
|
1362
1583
|
return False
|
|
1363
1584
|
|
|
1364
1585
|
|
|
1365
|
-
def get_download_directory(
|
|
1586
|
+
def get_download_directory(
|
|
1587
|
+
place: Literal[
|
|
1588
|
+
'temp',
|
|
1589
|
+
'script',
|
|
1590
|
+
'working'] = 'temp',
|
|
1591
|
+
script_path: str = None
|
|
1592
|
+
) -> str:
|
|
1366
1593
|
"""
|
|
1367
1594
|
The function returns the default download directory based on place.
|
|
1368
1595
|
|
|
@@ -1394,11 +1621,12 @@ def backup_folder(directory_path: str, backup_directory: str) -> None:
|
|
|
1394
1621
|
"""
|
|
1395
1622
|
Backup the specified directory.
|
|
1396
1623
|
|
|
1397
|
-
:param directory_path: The directory path to
|
|
1398
|
-
:param backup_directory: The directory to
|
|
1624
|
+
:param directory_path: The directory path to back up.
|
|
1625
|
+
:param backup_directory: The directory to back up the directory to.
|
|
1399
1626
|
|
|
1400
1627
|
Example:
|
|
1401
|
-
backup_folder(
|
|
1628
|
+
backup_folder(
|
|
1629
|
+
directory_path='C:\\Users\\user1\\Downloads\\folder1', backup_directory='C:\\Users\\user1\\Downloads\\backup')
|
|
1402
1630
|
|
|
1403
1631
|
Backed up folder will be moved to 'C:\\Users\\user1\\Downloads\\backup' with timestamp in the name.
|
|
1404
1632
|
Final path will look like: 'C:\\Users\\user1\\Downloads\\backup\\20231003-120000-000000_folder1'
|
|
@@ -1456,7 +1684,7 @@ def backup_file(
|
|
|
1456
1684
|
else:
|
|
1457
1685
|
file_name: str = f"{file_name_no_extension}_{timestamp}{file_extension}"
|
|
1458
1686
|
backup_file_path: str = str(Path(backup_directory) / file_name)
|
|
1459
|
-
|
|
1687
|
+
rename_file(file_path, file_name)
|
|
1460
1688
|
|
|
1461
1689
|
return backup_file_path
|
|
1462
1690
|
else:
|
|
@@ -1470,8 +1698,105 @@ def find_file(file_name: str, directory_path: str):
|
|
|
1470
1698
|
:param directory_path: string, The directory to search in.
|
|
1471
1699
|
:return:
|
|
1472
1700
|
"""
|
|
1473
|
-
for
|
|
1701
|
+
for dir_path, dir_names, filenames in os.walk(directory_path):
|
|
1474
1702
|
for filename in filenames:
|
|
1475
1703
|
if filename == file_name:
|
|
1476
|
-
return os.path.join(
|
|
1704
|
+
return os.path.join(dir_path, filename)
|
|
1477
1705
|
return None
|
|
1706
|
+
|
|
1707
|
+
|
|
1708
|
+
def create_ubuntu_desktop_shortcut(
|
|
1709
|
+
file_path: str = None,
|
|
1710
|
+
shortcut_name: str = None,
|
|
1711
|
+
command: str = None,
|
|
1712
|
+
working_directory: str = None,
|
|
1713
|
+
icon_path: str = None,
|
|
1714
|
+
terminal: bool = False,
|
|
1715
|
+
comment: str = "Shortcut to execute the Python script",
|
|
1716
|
+
categories: str = "Utility",
|
|
1717
|
+
set_executable: bool = False,
|
|
1718
|
+
set_trusted: bool = False,
|
|
1719
|
+
set_xfce_exe_checksum: bool = False
|
|
1720
|
+
):
|
|
1721
|
+
"""
|
|
1722
|
+
Create a desktop shortcut on Ubuntu.
|
|
1723
|
+
Either file_path or command must be specified.
|
|
1724
|
+
|
|
1725
|
+
:param file_path: string, The file_path to execute when the shortcut is clicked.
|
|
1726
|
+
Example2: '/path/to/script.sh'
|
|
1727
|
+
:param shortcut_name: string, The name of the shortcut.
|
|
1728
|
+
Example: 'My Python Script'
|
|
1729
|
+
Result: 'My Python Script.desktop'
|
|
1730
|
+
:param command: string, The command to execute when the shortcut is clicked.
|
|
1731
|
+
Example: 'python3 /path/to/script.py'
|
|
1732
|
+
:param working_directory: string, The working directory for the command.
|
|
1733
|
+
If None, the command will be executed in the same script's directory.
|
|
1734
|
+
:param icon_path: string, The path to the icon file.
|
|
1735
|
+
:param terminal: boolean, If True, the command will be executed in a terminal.
|
|
1736
|
+
:param comment: string, A comment to describe the shortcut.
|
|
1737
|
+
:param categories: string, The categories of the shortcut.
|
|
1738
|
+
:param set_executable: boolean, If True, the shortcut will be made executable.
|
|
1739
|
+
:param set_trusted: boolean, If True, the shortcut will be marked as trusted.
|
|
1740
|
+
This is needed for GNOME.
|
|
1741
|
+
:param set_xfce_exe_checksum: boolean, If True, the shortcut will be made safe executable for XFCE.
|
|
1742
|
+
|
|
1743
|
+
:return: None
|
|
1744
|
+
"""
|
|
1745
|
+
|
|
1746
|
+
if not file_path and not command:
|
|
1747
|
+
raise ValueError("Either 'file_path' or 'command' must be specified.")
|
|
1748
|
+
if command and file_path:
|
|
1749
|
+
raise ValueError("Only one of 'file_path' or 'command' can be specified.")
|
|
1750
|
+
if command and not shortcut_name:
|
|
1751
|
+
raise ValueError("The 'shortcut_name' must be specified when 'command' is used.")
|
|
1752
|
+
|
|
1753
|
+
from .permissions import ubuntu_permissions
|
|
1754
|
+
|
|
1755
|
+
# Get the user's directory.
|
|
1756
|
+
desktop_dir = os.path.expanduser("~/Desktop")
|
|
1757
|
+
|
|
1758
|
+
if not working_directory and file_path:
|
|
1759
|
+
working_directory = os.path.dirname(file_path)
|
|
1760
|
+
|
|
1761
|
+
if not shortcut_name:
|
|
1762
|
+
shortcut_name: str = Path(file_path).stem
|
|
1763
|
+
|
|
1764
|
+
if command:
|
|
1765
|
+
executable: str = command
|
|
1766
|
+
elif file_path:
|
|
1767
|
+
executable: str = file_path
|
|
1768
|
+
else:
|
|
1769
|
+
raise ValueError("Either 'file_path' or 'command' must be specified.")
|
|
1770
|
+
|
|
1771
|
+
# Full path to the .desktop file
|
|
1772
|
+
shortcut_path = os.path.join(desktop_dir, f"{shortcut_name}.desktop")
|
|
1773
|
+
|
|
1774
|
+
# Generate the content for the .desktop file
|
|
1775
|
+
desktop_entry = [
|
|
1776
|
+
"[Desktop Entry]",
|
|
1777
|
+
"Version=1.0",
|
|
1778
|
+
"Type=Application",
|
|
1779
|
+
f"Name={shortcut_name}",
|
|
1780
|
+
f"Exec={executable}",
|
|
1781
|
+
f"Path={working_directory}" if working_directory else "",
|
|
1782
|
+
f"Icon={icon_path}" if icon_path else "",
|
|
1783
|
+
f"Terminal={'true' if terminal else 'false'}",
|
|
1784
|
+
f"Comment={comment}",
|
|
1785
|
+
f"Categories={categories};",
|
|
1786
|
+
]
|
|
1787
|
+
|
|
1788
|
+
# Write the .desktop file
|
|
1789
|
+
with open(shortcut_path, "w") as shortcut_file:
|
|
1790
|
+
shortcut_file.write("\n".join(line for line in desktop_entry if line)) # Skip empty lines
|
|
1791
|
+
|
|
1792
|
+
# Make the .desktop file executable
|
|
1793
|
+
if set_executable:
|
|
1794
|
+
ubuntu_permissions.set_executable(shortcut_path)
|
|
1795
|
+
|
|
1796
|
+
# Mark the .desktop file as trusted
|
|
1797
|
+
if set_trusted:
|
|
1798
|
+
ubuntu_permissions.set_trusted_executable(shortcut_path)
|
|
1799
|
+
|
|
1800
|
+
# Make the .desktop file safe executable for XFCE
|
|
1801
|
+
if set_xfce_exe_checksum:
|
|
1802
|
+
ubuntu_permissions.set_xfce_exe_checksum(shortcut_path)
|