atomicshop 2.11.47__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/addons/process_list/compile.cmd +7 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
- 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/{addons/mains → a_mains}/msi_unpacker.py +3 -1
- 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/appointment_management.py +5 -3
- 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/dicts.py +12 -0
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/list_of_dicts.py +69 -5
- atomicshop/basics/lists.py +14 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/package_module.py +10 -0
- atomicshop/basics/strings.py +160 -7
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -4
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +12 -7
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +98 -0
- atomicshop/diff_check.py +340 -40
- atomicshop/dns.py +128 -12
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/const.py +38 -0
- atomicshop/etws/providers.py +21 -0
- atomicshop/etws/sessions.py +43 -0
- atomicshop/etws/trace.py +168 -0
- atomicshop/etws/traces/trace_dns.py +162 -0
- atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +222 -24
- atomicshop/file_io/docxs.py +35 -18
- atomicshop/file_io/file_io.py +35 -19
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +864 -293
- atomicshop/get_process_list.py +133 -0
- atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
- 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 -74
- 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 +257 -166
- atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
- atomicshop/monitor/change_monitor.py +96 -120
- atomicshop/monitor/checks/dns.py +139 -70
- atomicshop/monitor/checks/file.py +77 -0
- atomicshop/monitor/checks/network.py +81 -77
- atomicshop/monitor/checks/process_running.py +33 -34
- atomicshop/monitor/checks/url.py +94 -0
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +205 -0
- 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 -41
- atomicshop/process.py +63 -17
- atomicshop/process_poller/__init__.py +0 -0
- atomicshop/process_poller/pollers/__init__.py +0 -0
- atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
- atomicshop/process_poller/process_pool.py +207 -0
- atomicshop/process_poller/simple_process_pool.py +311 -0
- atomicshop/process_poller/tracer_base.py +45 -0
- atomicshop/process_poller/tracers/__init__.py +0 -0
- atomicshop/process_poller/tracers/event_log.py +46 -0
- atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/question_answer_engine.py +2 -2
- atomicshop/scheduling.py +24 -5
- atomicshop/sound.py +4 -2
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/startup/__init__.py +0 -0
- atomicshop/startup/win/__init__.py +0 -0
- atomicshop/startup/win/startup_folder.py +53 -0
- atomicshop/startup/win/task_scheduler.py +119 -0
- atomicshop/system_resource_monitor.py +61 -46
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/timer.py +30 -11
- atomicshop/urls.py +41 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +110 -25
- atomicshop/web_apis/__init__.py +0 -0
- 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/__init__.py +0 -0
- atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
- 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 +13 -9
- atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
- 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/factw/postgresql/firmware.py +4 -6
- atomicshop/wrappers/githubw.py +583 -51
- atomicshop/wrappers/loggingw/consts.py +49 -0
- atomicshop/wrappers/loggingw/filters.py +102 -0
- atomicshop/wrappers/loggingw/formatters.py +58 -71
- atomicshop/wrappers/loggingw/handlers.py +459 -40
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +1010 -178
- atomicshop/wrappers/loggingw/reading.py +344 -19
- atomicshop/wrappers/mongodbw/__init__.py +0 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
- 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 +81 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/psutilw/psutilw.py +9 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/console.py +34 -0
- atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
- atomicshop/wrappers/pywin32w/winshell.py +19 -0
- atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
- 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 +500 -173
- 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 +14 -9
- 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 +157 -0
- 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.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- 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/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/addons/process_list/compile.cmd +0 -2
- atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- 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/etw/dns_trace.py +0 -118
- atomicshop/etw/etw.py +0 -61
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -240
- atomicshop/monitor/checks/hash.py +0 -44
- atomicshop/monitor/checks/hash_checks/file.py +0 -55
- atomicshop/monitor/checks/hash_checks/url.py +0 -62
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -110
- atomicshop/process_poller.py +0 -237
- 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/loggingw/checks.py +0 -20
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.11.47.dist-info/RECORD +0 -251
- /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/mains → 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/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
- /atomicshop/{archiver → etws}/__init__.py +0 -0
- /atomicshop/{etw → etws/traces}/__init__.py +0 -0
- /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
- /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
atomicshop/filesystem.py
CHANGED
|
@@ -6,11 +6,15 @@ import shutil
|
|
|
6
6
|
import stat
|
|
7
7
|
import errno
|
|
8
8
|
from contextlib import contextmanager
|
|
9
|
+
from typing import Literal, Union
|
|
10
|
+
import tempfile
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
# noinspection PyPackageRequirements
|
|
13
|
+
import psutil
|
|
14
|
+
|
|
15
|
+
from .basics import strings, list_of_dicts, list_of_classes
|
|
12
16
|
from .file_io import file_io
|
|
13
|
-
from . import hashing
|
|
17
|
+
from . import hashing, datetimes, print_api
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
WINDOWS_DIRECTORY_SPECIAL_CHARACTERS = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
|
|
@@ -61,14 +65,18 @@ FILE_NAME_REPLACEMENT_DICT: dict = {
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
|
|
68
|
+
# class TimeCouldNotBeFoundInFileNameError(Exception):
|
|
69
|
+
# pass
|
|
70
|
+
|
|
71
|
+
|
|
64
72
|
def get_home_directory(return_sudo_user: bool = False) -> str:
|
|
65
73
|
"""
|
|
66
74
|
Returns the home directory of the current user or the user who invoked sudo.
|
|
67
75
|
|
|
68
76
|
:param return_sudo_user: bool, if 'False', then the function will return the home directory of the user who invoked
|
|
69
77
|
sudo (if the script was invoked with sudo).
|
|
70
|
-
If 'True', then the function will return the home directory of the current user, doesn't matter if the script
|
|
71
|
-
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.
|
|
72
80
|
"""
|
|
73
81
|
|
|
74
82
|
def return_home_directory_of_current_user():
|
|
@@ -113,6 +121,22 @@ def get_working_directory() -> str:
|
|
|
113
121
|
return str(Path.cwd())
|
|
114
122
|
|
|
115
123
|
|
|
124
|
+
def get_temp_directory() -> str:
|
|
125
|
+
"""
|
|
126
|
+
The function returns temporary directory of the system.
|
|
127
|
+
|
|
128
|
+
:return: string.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
# Get the temporary directory in 8.3 format
|
|
132
|
+
short_temp_dir = tempfile.gettempdir()
|
|
133
|
+
|
|
134
|
+
# Convert to the long path name
|
|
135
|
+
long_temp_dir = str(Path(short_temp_dir).resolve())
|
|
136
|
+
|
|
137
|
+
return long_temp_dir
|
|
138
|
+
|
|
139
|
+
|
|
116
140
|
def get_file_directory(file_path: str) -> str:
|
|
117
141
|
"""
|
|
118
142
|
The function will return directory of the file.
|
|
@@ -189,16 +213,6 @@ def get_list_of_directories_in_file_path(
|
|
|
189
213
|
return directory_list
|
|
190
214
|
|
|
191
215
|
|
|
192
|
-
def get_file_name(file_path: str) -> str:
|
|
193
|
-
"""
|
|
194
|
-
The function will return file name of the file.
|
|
195
|
-
|
|
196
|
-
:param file_path: string, full file path.
|
|
197
|
-
:return: string.
|
|
198
|
-
"""
|
|
199
|
-
return str(Path(file_path).name)
|
|
200
|
-
|
|
201
|
-
|
|
202
216
|
def check_absolute_path(filesystem_path) -> bool:
|
|
203
217
|
"""
|
|
204
218
|
The function checks if the path provided is a full path (absolute) or relative.
|
|
@@ -220,12 +234,12 @@ def check_absolute_path___add_full(filesystem_path: str, full_path_to_add: str)
|
|
|
220
234
|
"""
|
|
221
235
|
|
|
222
236
|
if not check_absolute_path(filesystem_path):
|
|
223
|
-
return f'{full_path_to_add}{os.sep}{
|
|
237
|
+
return f'{full_path_to_add}{os.sep}{remove_last_separator(filesystem_path)}'
|
|
224
238
|
else:
|
|
225
239
|
return filesystem_path
|
|
226
240
|
|
|
227
241
|
|
|
228
|
-
def
|
|
242
|
+
def is_file_exists(file_path: str) -> bool:
|
|
229
243
|
"""
|
|
230
244
|
Function to check if the path is a file.
|
|
231
245
|
|
|
@@ -240,7 +254,7 @@ def check_file_existence(file_path: str) -> bool:
|
|
|
240
254
|
return False
|
|
241
255
|
|
|
242
256
|
|
|
243
|
-
def
|
|
257
|
+
def is_directory_exists(directory_path: str) -> bool:
|
|
244
258
|
"""
|
|
245
259
|
Function to check if a path is a directory.
|
|
246
260
|
|
|
@@ -265,16 +279,20 @@ def remove_file(file_path: str, **kwargs) -> bool:
|
|
|
265
279
|
|
|
266
280
|
try:
|
|
267
281
|
os.remove(file_path)
|
|
268
|
-
print_api(f'File Removed: {file_path}')
|
|
282
|
+
print_api.print_api(f'File Removed: {file_path}')
|
|
269
283
|
return True
|
|
270
284
|
# Since the file doesn't exist, we actually don't care, since we want to remove it anyway.
|
|
271
285
|
except FileNotFoundError:
|
|
272
286
|
message = f'File Removal Failed, File non-existent: {file_path}'
|
|
273
|
-
print_api(message, error_type=True, logger_method='critical', **kwargs)
|
|
287
|
+
print_api.print_api(message, error_type=True, logger_method='critical', **kwargs)
|
|
274
288
|
return False
|
|
275
289
|
|
|
276
290
|
|
|
277
|
-
def remove_directory(
|
|
291
|
+
def remove_directory(
|
|
292
|
+
directory_path: str,
|
|
293
|
+
force_readonly: bool = False,
|
|
294
|
+
print_kwargs: dict = None
|
|
295
|
+
) -> bool:
|
|
278
296
|
"""
|
|
279
297
|
Remove directory if it exists.
|
|
280
298
|
|
|
@@ -306,15 +324,69 @@ def remove_directory(directory_path: str, force_readonly: bool = False, print_kw
|
|
|
306
324
|
shutil.rmtree(directory_path, onerror=remove_readonly)
|
|
307
325
|
else:
|
|
308
326
|
shutil.rmtree(directory_path)
|
|
309
|
-
print_api(f'Directory Removed: {directory_path}', **print_kwargs)
|
|
327
|
+
print_api.print_api(f'Directory Removed: {directory_path}', **print_kwargs)
|
|
310
328
|
return True
|
|
311
329
|
# Since the directory doesn't exist, we actually don't care, since we want to remove it anyway.
|
|
312
330
|
except FileNotFoundError:
|
|
313
331
|
message = f'Directory Removal Failed, Directory non-existent: {directory_path}'
|
|
314
|
-
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)
|
|
315
333
|
return False
|
|
316
334
|
|
|
317
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
|
+
|
|
318
390
|
def create_directory(directory_fullpath: str):
|
|
319
391
|
# Create directory if non-existent.
|
|
320
392
|
# The library is used to create folder if it doesn't exist and won't raise exception if it does
|
|
@@ -323,41 +395,143 @@ def create_directory(directory_fullpath: str):
|
|
|
323
395
|
pathlib.Path(directory_fullpath).mkdir(parents=True, exist_ok=True)
|
|
324
396
|
|
|
325
397
|
|
|
326
|
-
def rename_file(
|
|
398
|
+
def rename_file(file_path: str, new_file_name: str) -> None:
|
|
327
399
|
"""
|
|
328
400
|
The function renames file from source to target.
|
|
329
401
|
|
|
330
|
-
:param
|
|
331
|
-
: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.
|
|
332
404
|
|
|
333
405
|
:return: None
|
|
334
406
|
"""
|
|
335
407
|
|
|
408
|
+
renamed_file_path = str(Path(file_path).parent) + os.sep + new_file_name
|
|
409
|
+
|
|
336
410
|
# Rename file.
|
|
337
|
-
os.rename(
|
|
411
|
+
os.rename(file_path, renamed_file_path)
|
|
338
412
|
|
|
339
413
|
|
|
340
|
-
|
|
341
|
-
|
|
414
|
+
def rename_file_with_special_characters(
|
|
415
|
+
file_path: str,
|
|
416
|
+
rename_dictionary: dict = None,
|
|
417
|
+
) -> str:
|
|
342
418
|
"""
|
|
343
|
-
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.
|
|
344
421
|
|
|
345
422
|
:param file_path: string, full path to file.
|
|
346
|
-
:param
|
|
347
|
-
|
|
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.
|
|
467
|
+
|
|
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
|
|
486
|
+
|
|
487
|
+
if rename_dictionary is None:
|
|
488
|
+
rename_dictionary = FILE_NAME_REPLACEMENT_DICT
|
|
489
|
+
|
|
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
|
|
348
497
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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)
|
|
352
501
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
|
357
507
|
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
360
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
|
+
"""
|
|
361
535
|
|
|
362
536
|
original_name = file_path
|
|
363
537
|
try:
|
|
@@ -406,34 +580,86 @@ def temporary_change_working_directory(new_working_directory: str) -> None:
|
|
|
406
580
|
os.chdir(original_working_directory)
|
|
407
581
|
|
|
408
582
|
|
|
409
|
-
def move_file(source_file_path: str,
|
|
583
|
+
def move_file(source_file_path: str, target_directory: str, overwrite: bool = True) -> None:
|
|
410
584
|
"""
|
|
411
585
|
The function moves file from source to target.
|
|
412
586
|
|
|
413
587
|
:param source_file_path: string, full path to source file.
|
|
414
|
-
:param
|
|
588
|
+
:param target_directory: string, full path to target directory.
|
|
415
589
|
:param overwrite: boolean, if 'False', then the function will not overwrite the file if it exists.
|
|
416
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
|
+
|
|
417
605
|
:return: None
|
|
418
606
|
"""
|
|
419
607
|
|
|
608
|
+
target_file_path = target_directory + os.sep + Path(source_file_path).name
|
|
609
|
+
|
|
420
610
|
# Check if 'no_overwrite' is set to 'True' and if the file exists.
|
|
421
611
|
if not overwrite:
|
|
422
|
-
if
|
|
612
|
+
if is_file_exists(target_file_path):
|
|
423
613
|
raise FileExistsError(f'File already exists: {target_file_path}')
|
|
424
614
|
|
|
425
615
|
# Move file.
|
|
426
616
|
shutil.move(source_file_path, target_file_path)
|
|
427
617
|
|
|
428
618
|
|
|
429
|
-
def
|
|
619
|
+
def move_folder(source_directory: str, target_directory: str, overwrite: bool = True) -> None:
|
|
620
|
+
"""
|
|
621
|
+
The function moves folder from source to target.
|
|
622
|
+
|
|
623
|
+
:param source_directory: string, full path to source directory.
|
|
624
|
+
:param target_directory: string, full path to target directory.
|
|
625
|
+
:param overwrite: boolean, if 'False', then the function will not overwrite the directory if it exists.
|
|
626
|
+
|
|
627
|
+
:return: None
|
|
628
|
+
|
|
629
|
+
------------------------------
|
|
630
|
+
|
|
631
|
+
Example:
|
|
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)
|
|
640
|
+
|
|
641
|
+
Result path of the 'folder-to-move' will be:
|
|
642
|
+
'C:/Users/user1/Documents/folder-to-move'
|
|
643
|
+
|
|
644
|
+
"""
|
|
645
|
+
|
|
646
|
+
# Check if 'overwrite' is set to 'True' and if the directory exists.
|
|
647
|
+
if not overwrite:
|
|
648
|
+
if is_directory_exists(target_directory):
|
|
649
|
+
raise FileExistsError(f'Directory already exists: {target_directory}')
|
|
650
|
+
|
|
651
|
+
# Move directory.
|
|
652
|
+
shutil.move(source_directory, target_directory)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def move_top_level_files_from_folder_to_folder(
|
|
430
656
|
source_directory: str,
|
|
431
657
|
target_directory: str,
|
|
432
658
|
overwrite: bool = True
|
|
433
659
|
):
|
|
434
660
|
"""
|
|
435
|
-
The function is
|
|
436
|
-
|
|
661
|
+
The function is non-recursive to move only top level files from source directory to target directory
|
|
662
|
+
overwriting existing files.
|
|
437
663
|
|
|
438
664
|
:param source_directory: string, full path to source directory.
|
|
439
665
|
:param target_directory: string, full path to target directory.
|
|
@@ -441,26 +667,50 @@ def move_files_from_folder_to_folder(
|
|
|
441
667
|
"""
|
|
442
668
|
|
|
443
669
|
# Iterate over each item in the source directory
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
source_item = os.path.join(source_directory, item)
|
|
447
|
-
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)
|
|
448
672
|
|
|
673
|
+
for source_item in top_level_files:
|
|
449
674
|
# Move each item to the destination directory
|
|
450
|
-
move_file(source_file_path=source_item,
|
|
675
|
+
move_file(source_file_path=source_item, target_directory=target_directory, overwrite=overwrite)
|
|
451
676
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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)
|
|
464
714
|
|
|
465
715
|
|
|
466
716
|
def copy_file(
|
|
@@ -482,7 +732,7 @@ def copy_file(
|
|
|
482
732
|
|
|
483
733
|
# Check if 'no_overwrite' is set to 'True' and if the file exists.
|
|
484
734
|
if no_overwrite:
|
|
485
|
-
if
|
|
735
|
+
if is_file_exists(target_file_path):
|
|
486
736
|
raise FileExistsError(f'File already exists: {target_file_path}')
|
|
487
737
|
|
|
488
738
|
# Copy file.
|
|
@@ -505,56 +755,136 @@ def copy_directory(source_directory: str, target_directory: str, overwrite: bool
|
|
|
505
755
|
|
|
506
756
|
# Check if 'overwrite' is set to 'True' and if the directory exists.
|
|
507
757
|
if overwrite:
|
|
508
|
-
if
|
|
758
|
+
if is_directory_exists(target_directory):
|
|
509
759
|
remove_directory(target_directory)
|
|
510
760
|
|
|
511
761
|
# Copy directory.
|
|
512
762
|
shutil.copytree(source_directory, target_directory)
|
|
513
763
|
|
|
514
764
|
|
|
515
|
-
def
|
|
516
|
-
directory_path: str,
|
|
517
|
-
recursive: bool = True
|
|
518
|
-
) -> list:
|
|
765
|
+
def copy_files_from_folder_to_folder(source_directory: str, target_directory: str, overwrite: bool = False) -> None:
|
|
519
766
|
"""
|
|
520
|
-
|
|
521
|
-
The function receives a filesystem directory as string, scans it recursively for directories and returns list of
|
|
522
|
-
full paths to that directory (including).
|
|
767
|
+
The function will copy all the files from source directory to target directory.
|
|
523
768
|
|
|
524
|
-
:param
|
|
525
|
-
:param
|
|
526
|
-
|
|
527
|
-
'False', then the function will scan only in the directory that was passed.
|
|
528
|
-
|
|
529
|
-
:return: list of all found directory names with full paths.
|
|
769
|
+
:param source_directory: string, full path to source directory.
|
|
770
|
+
:param target_directory: string, full path to target directory.
|
|
771
|
+
:param overwrite: boolean, if 'True', then the function will overwrite the files if they exist.
|
|
530
772
|
"""
|
|
773
|
+
# Make sure the destination directory exists, if not create it
|
|
774
|
+
os.makedirs(target_directory, exist_ok=True)
|
|
531
775
|
|
|
532
|
-
#
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
# recursively.
|
|
537
|
-
for dirpath, subdirs, files in os.walk(directory_path):
|
|
538
|
-
# Iterate through all the directory names that were found in the folder.
|
|
539
|
-
for directory in subdirs:
|
|
540
|
-
# Get full directory path.
|
|
541
|
-
directory_list.append(os.path.join(dirpath, directory))
|
|
542
|
-
|
|
543
|
-
if not recursive:
|
|
544
|
-
break
|
|
776
|
+
# Copy contents of the source directory to the destination directory
|
|
777
|
+
for item in os.listdir(source_directory):
|
|
778
|
+
s = os.path.join(source_directory, item)
|
|
779
|
+
d = os.path.join(target_directory, item)
|
|
545
780
|
|
|
546
|
-
|
|
781
|
+
if os.path.isdir(s):
|
|
782
|
+
if os.path.exists(d) and not overwrite:
|
|
783
|
+
print(f"Directory {d} already exists. Skipping due to overwrite=False.")
|
|
784
|
+
else:
|
|
785
|
+
shutil.copytree(s, d, dirs_exist_ok=overwrite)
|
|
786
|
+
else:
|
|
787
|
+
if os.path.exists(d) and not overwrite:
|
|
788
|
+
print(f"File {d} already exists. Skipping due to overwrite=False.")
|
|
789
|
+
else:
|
|
790
|
+
shutil.copy2(s, d)
|
|
791
|
+
|
|
792
|
+
|
|
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)
|
|
547
870
|
|
|
548
871
|
|
|
549
|
-
def
|
|
872
|
+
def get_paths_from_directory(
|
|
550
873
|
directory_path: str,
|
|
874
|
+
simple_list: bool = False,
|
|
875
|
+
get_file: bool = False,
|
|
876
|
+
get_directory: bool = False,
|
|
551
877
|
recursive: bool = True,
|
|
552
878
|
file_name_check_pattern: str = '*',
|
|
879
|
+
datetime_format: str = None,
|
|
880
|
+
specific_date: str = None,
|
|
553
881
|
add_relative_directory: bool = False,
|
|
554
882
|
relative_file_name_as_directory: bool = False,
|
|
555
883
|
add_last_modified_time: bool = False,
|
|
556
|
-
sort_by_last_modified_time: bool = False
|
|
557
|
-
|
|
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]]:
|
|
558
888
|
"""
|
|
559
889
|
Recursive, by option.
|
|
560
890
|
The function receives a filesystem directory as string, scans it recursively for files and returns list of
|
|
@@ -563,15 +893,23 @@ def get_file_paths_from_directory(
|
|
|
563
893
|
of that tuple.
|
|
564
894
|
|
|
565
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.
|
|
566
899
|
:param recursive: boolean.
|
|
567
900
|
'True', then the function will scan recursively in subdirectories.
|
|
568
901
|
'False', then the function will scan only in the directory that was passed.
|
|
569
902
|
:param file_name_check_pattern: string, if specified, the function will return only files that match the pattern.
|
|
570
903
|
The string can contain part of file name to check or full file name with extension.
|
|
571
904
|
Can contain wildcards.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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.
|
|
575
913
|
:param add_relative_directory: boolean, if
|
|
576
914
|
'True', then the function will add relative directory to the output list.
|
|
577
915
|
In this case the output list will contain dictionaries with keys 'path' and 'relative_dir'.
|
|
@@ -583,47 +921,72 @@ def get_file_paths_from_directory(
|
|
|
583
921
|
to the output list.
|
|
584
922
|
:param sort_by_last_modified_time: boolean, if 'True', then the function will sort the output list by last
|
|
585
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.
|
|
586
928
|
|
|
587
929
|
:return: list of all found filenames with full file paths, list with relative folders to file excluding the
|
|
588
930
|
main folder.
|
|
589
931
|
"""
|
|
590
932
|
|
|
591
|
-
def
|
|
933
|
+
def get_path(file_or_directory: str):
|
|
592
934
|
"""
|
|
593
935
|
Function gets the full file path, adds it to the found 'object_list' and gets the relative path to that
|
|
594
936
|
file, against the main path to directory that was passed to the parent function.
|
|
595
937
|
"""
|
|
596
938
|
|
|
597
|
-
|
|
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)
|
|
598
941
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
file_result: dict = dict()
|
|
942
|
+
if simple_list:
|
|
943
|
+
object_list.append(file_or_dir_path)
|
|
944
|
+
return
|
|
603
945
|
|
|
604
|
-
|
|
605
|
-
|
|
946
|
+
path_object: AtomicPath = AtomicPath(path=file_or_dir_path)
|
|
947
|
+
path_object.queried_directory = directory_path
|
|
606
948
|
|
|
607
949
|
if add_relative_directory:
|
|
608
950
|
# if 'relative_file_name_as_directory' was passed.
|
|
609
951
|
if relative_file_name_as_directory:
|
|
610
952
|
# Output the path with filename.
|
|
611
|
-
|
|
612
|
-
directory_path,
|
|
953
|
+
path_object.relative_dir = _get_relative_output_path_from_input_path(
|
|
954
|
+
directory_path, dir_path, file_or_directory)
|
|
613
955
|
# if 'relative_file_name_as_directory' wasn't passed.
|
|
614
956
|
else:
|
|
615
957
|
# Output the path without filename.
|
|
616
|
-
|
|
958
|
+
path_object.relative_dir = _get_relative_output_path_from_input_path(
|
|
959
|
+
directory_path, dir_path)
|
|
617
960
|
|
|
618
961
|
# Remove separator from the beginning if exists.
|
|
619
|
-
|
|
962
|
+
path_object.relative_dir = path_object.relative_dir.removeprefix(os.sep)
|
|
620
963
|
|
|
621
964
|
# If 'add_last_modified_time' was passed.
|
|
622
965
|
if add_last_modified_time:
|
|
623
966
|
# Get last modified time of the file.
|
|
624
|
-
|
|
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
|
|
625
975
|
|
|
626
|
-
|
|
976
|
+
if specific_date:
|
|
977
|
+
if path_object.datetime_string != specific_date:
|
|
978
|
+
return
|
|
979
|
+
|
|
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".')
|
|
627
990
|
|
|
628
991
|
if sort_by_last_modified_time and not add_last_modified_time:
|
|
629
992
|
raise ValueError('Parameter "sort_by_last_modified_time" cannot be "True" if parameter '
|
|
@@ -632,18 +995,25 @@ def get_file_paths_from_directory(
|
|
|
632
995
|
raise ValueError('Parameter "relative_file_name_as_directory" cannot be "True" if parameter '
|
|
633
996
|
'"add_relative_directory" is not "True".')
|
|
634
997
|
|
|
998
|
+
if not datetime_format and specific_date:
|
|
999
|
+
raise ValueError('If "specific_date" is specified, "datetime_format" must be specified.')
|
|
1000
|
+
|
|
635
1001
|
# === Function main ================
|
|
636
1002
|
# Define locals.
|
|
637
1003
|
object_list: list = list()
|
|
638
1004
|
|
|
639
1005
|
# "Walk" over all the directories and subdirectories - make list of full file paths inside the directory
|
|
640
1006
|
# recursively.
|
|
641
|
-
for
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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)
|
|
647
1017
|
|
|
648
1018
|
if not recursive:
|
|
649
1019
|
break
|
|
@@ -651,7 +1021,34 @@ def get_file_paths_from_directory(
|
|
|
651
1021
|
# If 'sort_by_last_modified_time' was passed.
|
|
652
1022
|
if sort_by_last_modified_time:
|
|
653
1023
|
# Sort the list by last modified time.
|
|
654
|
-
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)
|
|
655
1052
|
|
|
656
1053
|
return object_list
|
|
657
1054
|
|
|
@@ -726,17 +1123,6 @@ def remove_last_separator(directory_path: str) -> str:
|
|
|
726
1123
|
return directory_path.removesuffix(os.sep)
|
|
727
1124
|
|
|
728
1125
|
|
|
729
|
-
def remove_first_separator(filesystem_path: str) -> str:
|
|
730
|
-
"""
|
|
731
|
-
The function removes the first character in 'filesystem_path' if it is a separator returning the processed string.
|
|
732
|
-
If the first character is not a separator, nothing is happening.
|
|
733
|
-
|
|
734
|
-
:param filesystem_path:
|
|
735
|
-
:return:
|
|
736
|
-
"""
|
|
737
|
-
return filesystem_path.removesuffix(os.sep)
|
|
738
|
-
|
|
739
|
-
|
|
740
1126
|
def add_last_separator(filesystem_path: str) -> str:
|
|
741
1127
|
"""
|
|
742
1128
|
The function adds a separator to the end of the path if it doesn't exist.
|
|
@@ -772,14 +1158,14 @@ def get_files_and_folders(directory_path: str, string_contains: str = str()):
|
|
|
772
1158
|
return files_folders_list
|
|
773
1159
|
|
|
774
1160
|
|
|
775
|
-
def get_file_modified_time(
|
|
1161
|
+
def get_file_modified_time(file_or_dir_path: str) -> float:
|
|
776
1162
|
"""
|
|
777
1163
|
The function returns the time of last modification of the file in seconds since the epoch.
|
|
778
1164
|
|
|
779
|
-
:param
|
|
1165
|
+
:param file_or_dir_path: string, full path to file or directory.
|
|
780
1166
|
:return: float, time of last modification of the file in seconds since the epoch.
|
|
781
1167
|
"""
|
|
782
|
-
return os.path.getmtime(
|
|
1168
|
+
return os.path.getmtime(file_or_dir_path)
|
|
783
1169
|
|
|
784
1170
|
|
|
785
1171
|
def change_last_modified_date_of_file(file_path: str, new_date: float) -> None:
|
|
@@ -801,43 +1187,6 @@ def change_last_modified_date_of_file(file_path: str, new_date: float) -> None:
|
|
|
801
1187
|
os.utime(file_path, (new_date, new_date))
|
|
802
1188
|
|
|
803
1189
|
|
|
804
|
-
def get_file_hashes_from_directory(directory_path: str, recursive: bool = False, add_binary: bool = False) -> list:
|
|
805
|
-
"""
|
|
806
|
-
The function scans a directory for files and returns a list of dictionaries with file path and hash of the file.
|
|
807
|
-
Binary option can be specified.
|
|
808
|
-
|
|
809
|
-
:param directory_path: string, of full path to directory you want to return file names of.
|
|
810
|
-
:param recursive: boolean.
|
|
811
|
-
'True', then the function will scan recursively in subdirectories.
|
|
812
|
-
'False', then the function will scan only in the directory that was passed.
|
|
813
|
-
:param add_binary: boolean, if 'True', then the function will add the binary of the file to the output list.
|
|
814
|
-
|
|
815
|
-
:return: list of dicts with full file paths, hashes and binaries (if specified).
|
|
816
|
-
"""
|
|
817
|
-
|
|
818
|
-
# Get all the files.
|
|
819
|
-
file_paths_list = get_file_paths_from_directory(directory_path, recursive=recursive)
|
|
820
|
-
|
|
821
|
-
# Create a list of dictionaries, each dictionary is a file with its hash.
|
|
822
|
-
files: list = list()
|
|
823
|
-
for file_index, file_path in enumerate(file_paths_list):
|
|
824
|
-
print_status_of_list(
|
|
825
|
-
list_instance=file_paths_list, prefix_string=f'Reading File: ', current_state=(file_index + 1))
|
|
826
|
-
|
|
827
|
-
file_info: dict = dict()
|
|
828
|
-
file_info['path'] = file_path['path']
|
|
829
|
-
|
|
830
|
-
if add_binary:
|
|
831
|
-
file_info['binary'] = file_io.read_file(file_path['path'], file_mode='rb', stdout=False)
|
|
832
|
-
file_info['hash'] = hashing.hash_bytes(file_info['binary'])
|
|
833
|
-
else:
|
|
834
|
-
file_info['hash'] = hashing.hash_file(file_path['path'])
|
|
835
|
-
|
|
836
|
-
files.append(file_info)
|
|
837
|
-
|
|
838
|
-
return files
|
|
839
|
-
|
|
840
|
-
|
|
841
1190
|
def find_duplicates_by_hash(
|
|
842
1191
|
directory_path: str,
|
|
843
1192
|
recursive: bool = False,
|
|
@@ -856,33 +1205,34 @@ def find_duplicates_by_hash(
|
|
|
856
1205
|
"""
|
|
857
1206
|
|
|
858
1207
|
# Get all the files.
|
|
859
|
-
files: list =
|
|
1208
|
+
files: list = get_paths_from_directory(
|
|
1209
|
+
directory_path, get_file=True, recursive=recursive, add_file_binary=add_binary)
|
|
860
1210
|
|
|
861
1211
|
same_hash_files: list = list()
|
|
862
1212
|
# Check if there are files that have exactly the same hash.
|
|
863
|
-
for
|
|
1213
|
+
for atomic_path in files:
|
|
864
1214
|
# Create a list of files that have the same hash for current 'firmware'.
|
|
865
1215
|
current_run_list: list = list()
|
|
866
|
-
for
|
|
1216
|
+
for atomic_path_compare in files:
|
|
867
1217
|
# Add all the 'firmware_compare' that have the same hash to the list.
|
|
868
|
-
if (
|
|
869
|
-
|
|
1218
|
+
if (atomic_path.hash == atomic_path_compare.hash and
|
|
1219
|
+
atomic_path.path != atomic_path_compare.path):
|
|
870
1220
|
# Check if current 'firmware' is already in the 'same_hash_files' list. If not, add 'firmware_compare'
|
|
871
1221
|
# to the 'current_run_list'.
|
|
872
1222
|
if not any(list_of_dicts.is_value_exist_in_key(
|
|
873
|
-
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
|
|
874
1224
|
test_hash in same_hash_files):
|
|
875
1225
|
current_run_list.append({
|
|
876
|
-
'path':
|
|
877
|
-
'hash':
|
|
1226
|
+
'path': atomic_path_compare.path,
|
|
1227
|
+
'hash': atomic_path_compare.hash
|
|
878
1228
|
})
|
|
879
1229
|
|
|
880
1230
|
if current_run_list:
|
|
881
1231
|
# After the iteration of the 'firmware_compare' finished and the list is not empty, add the 'firmware'
|
|
882
1232
|
# to the list.
|
|
883
1233
|
current_run_list.append({
|
|
884
|
-
'path':
|
|
885
|
-
'hash':
|
|
1234
|
+
'path': atomic_path.path,
|
|
1235
|
+
'hash': atomic_path.hash
|
|
886
1236
|
})
|
|
887
1237
|
same_hash_files.append(current_run_list)
|
|
888
1238
|
|
|
@@ -963,39 +1313,40 @@ def get_directory_size(directory_path: str):
|
|
|
963
1313
|
|
|
964
1314
|
|
|
965
1315
|
def get_subpaths_between(start_path: str, end_path: str) -> list[str]:
|
|
1316
|
+
# noinspection GrazieInspection
|
|
966
1317
|
"""
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1318
|
+
Get the subpaths between two paths.
|
|
1319
|
+
:param start_path: string, start path.
|
|
1320
|
+
:param end_path: string, end path.
|
|
1321
|
+
:return:
|
|
971
1322
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1323
|
+
Example Linux:
|
|
1324
|
+
start_path = '/test/1'
|
|
1325
|
+
end_path = '/test/1/2/3/4'
|
|
975
1326
|
|
|
976
|
-
|
|
1327
|
+
subpaths = get_subpaths_between(start_path, end_path)
|
|
977
1328
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1329
|
+
subpaths = [
|
|
1330
|
+
'/test/1'
|
|
1331
|
+
'/test/1/2',
|
|
1332
|
+
'/test/1/2/3',
|
|
1333
|
+
'/test/1/2/3/4',
|
|
1334
|
+
]
|
|
984
1335
|
|
|
985
1336
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1337
|
+
Example Windows:
|
|
1338
|
+
start_path = 'C:\\test\\1'
|
|
1339
|
+
end_path = 'C:\\test\\1\\2\\3\\4'
|
|
989
1340
|
|
|
990
|
-
|
|
1341
|
+
subpaths = get_subpaths_between(start_path, end_path)
|
|
991
1342
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
+
"""
|
|
999
1350
|
|
|
1000
1351
|
# Detect slash type based on the input (default to forward slash)
|
|
1001
1352
|
slash_type = "\\" if "\\" in start_path else "/"
|
|
@@ -1036,13 +1387,13 @@ def get_subpaths_between(start_path: str, end_path: str) -> list[str]:
|
|
|
1036
1387
|
# else:
|
|
1037
1388
|
# raise ValueError("Start path must be a parent of the end path")
|
|
1038
1389
|
#
|
|
1039
|
-
# # Reverse the list so it goes from start to end.
|
|
1390
|
+
# # Reverse the list, so it goes from start to end.
|
|
1040
1391
|
# subpaths.reverse()
|
|
1041
1392
|
#
|
|
1042
1393
|
# return subpaths
|
|
1043
1394
|
|
|
1044
1395
|
|
|
1045
|
-
def create_dict_of_paths_list(list_of_paths: list) ->
|
|
1396
|
+
def create_dict_of_paths_list(list_of_paths: list) -> list:
|
|
1046
1397
|
"""
|
|
1047
1398
|
The function receives a list of paths and returns a dictionary with keys as the paths and values as the
|
|
1048
1399
|
subpaths of the key path.
|
|
@@ -1078,7 +1429,7 @@ def create_dict_of_paths_list(list_of_paths: list) -> dict:
|
|
|
1078
1429
|
:return: dictionary.
|
|
1079
1430
|
"""
|
|
1080
1431
|
|
|
1081
|
-
structure = []
|
|
1432
|
+
structure: list = []
|
|
1082
1433
|
for path in list_of_paths:
|
|
1083
1434
|
create_dict_of_path(path, structure)
|
|
1084
1435
|
return structure
|
|
@@ -1086,70 +1437,20 @@ def create_dict_of_paths_list(list_of_paths: list) -> dict:
|
|
|
1086
1437
|
|
|
1087
1438
|
def create_dict_of_path(
|
|
1088
1439
|
path: str,
|
|
1089
|
-
# structure_dict: dict,
|
|
1090
1440
|
structure_list: list,
|
|
1091
|
-
add_data_to_entry: any = None
|
|
1092
|
-
add_data_key: str = 'addon',
|
|
1093
|
-
parent_entry: str = None
|
|
1441
|
+
add_data_to_entry: list[dict[str, any]] = None
|
|
1094
1442
|
):
|
|
1095
1443
|
"""
|
|
1096
1444
|
The function receives a path and a list, and adds the path to the list.
|
|
1097
|
-
|
|
1098
1445
|
Check the working example from 'create_dict_of_paths_list' function.
|
|
1099
1446
|
|
|
1100
1447
|
:param path: string, path.
|
|
1101
1448
|
:param structure_list: list to add the path to.
|
|
1102
|
-
:param add_data_to_entry:
|
|
1103
|
-
|
|
1104
|
-
: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}
|
|
1105
1451
|
:return:
|
|
1106
1452
|
"""
|
|
1107
1453
|
|
|
1108
|
-
# # Normalize path for cross-platform compatibility
|
|
1109
|
-
# normalized_path = path.replace("\\", "/")
|
|
1110
|
-
# parts = normalized_path.strip("/").split("/")
|
|
1111
|
-
# current_level = structure_dict
|
|
1112
|
-
#
|
|
1113
|
-
# for part in parts[:-1]: # Iterate through the directories
|
|
1114
|
-
# # If the part is not already a key in the current level of the structure, add it
|
|
1115
|
-
# if part not in current_level:
|
|
1116
|
-
# current_level[part] = {}
|
|
1117
|
-
# current_level = current_level[part]
|
|
1118
|
-
#
|
|
1119
|
-
# # Create the entry for the file with additional data
|
|
1120
|
-
# file_entry = {"entry": parts[-1], add_data_key: add_data_to_entry}
|
|
1121
|
-
#
|
|
1122
|
-
# # We're adding file entries under numeric keys.
|
|
1123
|
-
# if isinstance(current_level, dict) and all(isinstance(key, int) for key in current_level.keys()):
|
|
1124
|
-
# current_level[len(current_level)] = file_entry
|
|
1125
|
-
# else:
|
|
1126
|
-
# # Handle cases where there's a mix of numeric keys and directory names
|
|
1127
|
-
# # Find the next available numeric key
|
|
1128
|
-
# next_key = max([key if isinstance(key, int) else -1 for key in current_level.keys()], default=-1) + 1
|
|
1129
|
-
# current_level[next_key] = file_entry
|
|
1130
|
-
|
|
1131
|
-
# entries_key_name = "__entries__"
|
|
1132
|
-
#
|
|
1133
|
-
# # Normalize path for cross-platform compatibility
|
|
1134
|
-
# normalized_path = path.replace("\\", "/")
|
|
1135
|
-
# parts = normalized_path.strip("/").split("/")
|
|
1136
|
-
# current_level = structure_dict
|
|
1137
|
-
#
|
|
1138
|
-
# for part in parts[:-1]: # Navigate through or create directory structure
|
|
1139
|
-
# if part not in current_level:
|
|
1140
|
-
# current_level[part] = {}
|
|
1141
|
-
# current_level = current_level[part]
|
|
1142
|
-
#
|
|
1143
|
-
# # Create the entry for the file with additional data
|
|
1144
|
-
# file_entry = {"entry": parts[-1], add_data_key: add_data_to_entry}
|
|
1145
|
-
#
|
|
1146
|
-
# # If the current level (final directory) does not have an "entries" key for files, create it
|
|
1147
|
-
# if entries_key_name not in current_level:
|
|
1148
|
-
# current_level[entries_key_name] = []
|
|
1149
|
-
#
|
|
1150
|
-
# # Append the file entry to the list associated with the "entries" key
|
|
1151
|
-
# current_level[entries_key_name].append(file_entry)
|
|
1152
|
-
|
|
1153
1454
|
# Normalize path for cross-platform compatibility
|
|
1154
1455
|
normalized_path = path.replace("\\", "/")
|
|
1155
1456
|
parts = normalized_path.strip("/").split("/")
|
|
@@ -1157,75 +1458,345 @@ def create_dict_of_path(
|
|
|
1157
1458
|
current_level = structure_list
|
|
1158
1459
|
|
|
1159
1460
|
for i, part in enumerate(parts):
|
|
1160
|
-
# 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)
|
|
1161
1462
|
is_last_part = (i == len(parts) - 1)
|
|
1162
1463
|
|
|
1163
1464
|
# Try to find an existing entry for this part
|
|
1164
1465
|
existing_entry = next((item for item in current_level if item["entry"] == part), None)
|
|
1165
1466
|
|
|
1166
1467
|
if existing_entry is None:
|
|
1167
|
-
#
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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)
|
|
1172
1475
|
|
|
1173
1476
|
current_level.append(new_entry)
|
|
1477
|
+
|
|
1174
1478
|
# Only update current_level if it's not the last part
|
|
1175
1479
|
if not is_last_part:
|
|
1176
1480
|
current_level = new_entry["included"]
|
|
1177
1481
|
else:
|
|
1178
|
-
# If it's not the last part
|
|
1482
|
+
# If the entry exists and it's not the last part, navigate deeper
|
|
1179
1483
|
if not is_last_part:
|
|
1180
1484
|
current_level = existing_entry["included"]
|
|
1181
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
|
+
|
|
1491
|
+
|
|
1492
|
+
def list_open_files_in_directory(directory):
|
|
1493
|
+
"""
|
|
1494
|
+
The function lists all open files by any processes in the directory.
|
|
1495
|
+
:param directory:
|
|
1496
|
+
:return:
|
|
1497
|
+
"""
|
|
1498
|
+
open_files: list = []
|
|
1499
|
+
|
|
1500
|
+
# Iterate over all running processes
|
|
1501
|
+
for proc in psutil.process_iter(['pid', 'name']):
|
|
1502
|
+
try:
|
|
1503
|
+
# List open files for the process
|
|
1504
|
+
proc_open_files = proc.open_files()
|
|
1505
|
+
for file in proc_open_files:
|
|
1506
|
+
if file.path.startswith(directory):
|
|
1507
|
+
# noinspection PyUnresolvedReferences
|
|
1508
|
+
open_files.append((proc.info['pid'], proc.info['name'], file.path))
|
|
1509
|
+
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
|
1510
|
+
# Ignore processes that can't be accessed
|
|
1511
|
+
continue
|
|
1512
|
+
|
|
1513
|
+
return open_files
|
|
1182
1514
|
|
|
1183
|
-
|
|
1515
|
+
|
|
1516
|
+
def is_any_file_in_directory_opened_by_process(directory_path: str) -> bool:
|
|
1184
1517
|
"""
|
|
1185
|
-
The function checks if any file in the directory is
|
|
1186
|
-
it will be locked for reading). Basically it opens a handle for read and write and if it fails, then the file is
|
|
1187
|
-
locked.
|
|
1518
|
+
The function checks if any file in the directory is opened in any process using psutil.
|
|
1188
1519
|
|
|
1189
1520
|
:param directory_path: string, full path to directory.
|
|
1190
|
-
:return: boolean, if 'True', then at least one file in the directory is
|
|
1191
|
-
"""
|
|
1521
|
+
:return: boolean, if 'True', then at least one file in the directory is opened by a process.
|
|
1522
|
+
"""
|
|
1523
|
+
|
|
1524
|
+
# for filename in os.listdir(directory_path):
|
|
1525
|
+
# file_path = os.path.join(directory_path, filename)
|
|
1526
|
+
# if os.path.isfile(file_path):
|
|
1527
|
+
# return is_file_locked(file_path)
|
|
1528
|
+
# return False
|
|
1529
|
+
|
|
1530
|
+
# Iterate over all running processes
|
|
1531
|
+
for proc in psutil.process_iter(['pid', 'name']):
|
|
1532
|
+
try:
|
|
1533
|
+
# List open files for the process
|
|
1534
|
+
proc_open_files = proc.open_files()
|
|
1535
|
+
for file in proc_open_files:
|
|
1536
|
+
if file.path.startswith(directory_path):
|
|
1537
|
+
return True
|
|
1538
|
+
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
|
1539
|
+
# Ignore processes that can't be accessed
|
|
1540
|
+
continue
|
|
1192
1541
|
|
|
1193
|
-
for filename in os.listdir(directory_path):
|
|
1194
|
-
file_path = os.path.join(directory_path, filename)
|
|
1195
|
-
if os.path.isfile(file_path):
|
|
1196
|
-
return is_file_locked(file_path)
|
|
1197
1542
|
return False
|
|
1198
1543
|
|
|
1199
1544
|
|
|
1200
|
-
def
|
|
1545
|
+
def is_any_file_in_list_open_by_process(file_paths_list: list[str]) -> bool:
|
|
1201
1546
|
"""
|
|
1202
|
-
The function checks if any file in the list is
|
|
1203
|
-
for reading). Basically it opens a handle for read and write and if it fails, then the file is locked.
|
|
1547
|
+
The function checks if any file in the list is opened by any running process.
|
|
1204
1548
|
|
|
1205
1549
|
:param file_paths_list: list of strings, full paths to files.
|
|
1206
1550
|
:return: boolean, if 'True', then at least one file in the list is locked.
|
|
1207
1551
|
"""
|
|
1208
1552
|
|
|
1209
1553
|
for file_path in file_paths_list:
|
|
1210
|
-
if
|
|
1554
|
+
if is_file_open_by_process(file_path):
|
|
1211
1555
|
return True
|
|
1212
1556
|
return False
|
|
1213
1557
|
|
|
1214
1558
|
|
|
1215
|
-
def
|
|
1559
|
+
def is_file_open_by_process(file_path: str) -> bool:
|
|
1216
1560
|
"""
|
|
1217
|
-
The function checks if the file is
|
|
1218
|
-
Basically it opens a handle for read and write and if it fails, then the file is locked.
|
|
1561
|
+
The function checks if the file is opened in any of the running processes.
|
|
1219
1562
|
|
|
1220
1563
|
:param file_path: string, full path to file.
|
|
1221
1564
|
:return: boolean, if 'True', then the file is locked.
|
|
1222
1565
|
"""
|
|
1223
1566
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1567
|
+
# If the file doesn't exist, or it is not a file, it's not locked.
|
|
1568
|
+
if not os.path.isfile(file_path):
|
|
1569
|
+
return False
|
|
1570
|
+
|
|
1571
|
+
# Iterate over all running processes
|
|
1572
|
+
for proc in psutil.process_iter(['pid', 'name']):
|
|
1573
|
+
try:
|
|
1574
|
+
# List open files for the process
|
|
1575
|
+
proc_open_files = proc.open_files()
|
|
1576
|
+
for file in proc_open_files:
|
|
1577
|
+
if file.path == file_path:
|
|
1578
|
+
return True
|
|
1579
|
+
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
|
1580
|
+
# Ignore processes that can't be accessed
|
|
1581
|
+
continue
|
|
1582
|
+
|
|
1231
1583
|
return False
|
|
1584
|
+
|
|
1585
|
+
|
|
1586
|
+
def get_download_directory(
|
|
1587
|
+
place: Literal[
|
|
1588
|
+
'temp',
|
|
1589
|
+
'script',
|
|
1590
|
+
'working'] = 'temp',
|
|
1591
|
+
script_path: str = None
|
|
1592
|
+
) -> str:
|
|
1593
|
+
"""
|
|
1594
|
+
The function returns the default download directory based on place.
|
|
1595
|
+
|
|
1596
|
+
:param place: string,
|
|
1597
|
+
'temp', then the function will return the temporary directory.
|
|
1598
|
+
'script', then the function will return the directory of the script.
|
|
1599
|
+
'working', then the function will return the working directory.
|
|
1600
|
+
:param script_path: string, full path to the script.
|
|
1601
|
+
:return: string, full path to the default download directory.
|
|
1602
|
+
"""
|
|
1603
|
+
|
|
1604
|
+
if place == 'script' and script_path is None:
|
|
1605
|
+
raise ValueError("Script path must be specified if place is 'script'.")
|
|
1606
|
+
|
|
1607
|
+
# Get the download directory based on the operating system
|
|
1608
|
+
if place == 'script':
|
|
1609
|
+
download_directory = get_file_directory(script_path)
|
|
1610
|
+
elif place == 'working':
|
|
1611
|
+
download_directory = get_working_directory()
|
|
1612
|
+
elif place == 'temp':
|
|
1613
|
+
download_directory = get_temp_directory()
|
|
1614
|
+
else:
|
|
1615
|
+
raise ValueError("Invalid place specified.")
|
|
1616
|
+
|
|
1617
|
+
return download_directory
|
|
1618
|
+
|
|
1619
|
+
|
|
1620
|
+
def backup_folder(directory_path: str, backup_directory: str) -> None:
|
|
1621
|
+
"""
|
|
1622
|
+
Backup the specified directory.
|
|
1623
|
+
|
|
1624
|
+
:param directory_path: The directory path to back up.
|
|
1625
|
+
:param backup_directory: The directory to back up the directory to.
|
|
1626
|
+
|
|
1627
|
+
Example:
|
|
1628
|
+
backup_folder(
|
|
1629
|
+
directory_path='C:\\Users\\user1\\Downloads\\folder1', backup_directory='C:\\Users\\user1\\Downloads\\backup')
|
|
1630
|
+
|
|
1631
|
+
Backed up folder will be moved to 'C:\\Users\\user1\\Downloads\\backup' with timestamp in the name.
|
|
1632
|
+
Final path will look like: 'C:\\Users\\user1\\Downloads\\backup\\20231003-120000-000000_folder1'
|
|
1633
|
+
"""
|
|
1634
|
+
|
|
1635
|
+
if is_directory_exists(directory_path):
|
|
1636
|
+
timestamp: str = datetimes.TimeFormats().get_current_formatted_time_filename_stamp(True)
|
|
1637
|
+
directory_name = Path(directory_path).name
|
|
1638
|
+
backup_directory_path: str = str(Path(backup_directory) / f"{timestamp}_{directory_name}")
|
|
1639
|
+
create_directory(backup_directory_path)
|
|
1640
|
+
move_folder(directory_path, backup_directory_path)
|
|
1641
|
+
|
|
1642
|
+
|
|
1643
|
+
def backup_file(
|
|
1644
|
+
file_path: str,
|
|
1645
|
+
backup_directory: str,
|
|
1646
|
+
timestamp_as_prefix: bool = False
|
|
1647
|
+
) -> Union[str, None]:
|
|
1648
|
+
"""
|
|
1649
|
+
Backup the specified file.
|
|
1650
|
+
|
|
1651
|
+
:param file_path: The file path to back up.
|
|
1652
|
+
:param backup_directory: The directory to back up the file to.
|
|
1653
|
+
:param timestamp_as_prefix: boolean, if
|
|
1654
|
+
True, then the timestamp will be added as a prefix to the file name.
|
|
1655
|
+
False, then the timestamp will be added as a suffix to the file name.
|
|
1656
|
+
-----------------------------------------
|
|
1657
|
+
Example:
|
|
1658
|
+
backup_file(
|
|
1659
|
+
file_path='C:\\Users\\user1\\Downloads\\file.txt',
|
|
1660
|
+
backup_directory='C:\\Users\\user1\\Downloads\\backup',
|
|
1661
|
+
timestamp_as_prefix=True
|
|
1662
|
+
)
|
|
1663
|
+
|
|
1664
|
+
Backed up file will be moved to 'C:\\Users\\user1\\Downloads\\backup' with timestamp in the name.
|
|
1665
|
+
Final path will look like: 'C:\\Users\\user1\\Downloads\\backup\\20231003-120000-000000_file.txt'
|
|
1666
|
+
---------------------------------------------
|
|
1667
|
+
Example when timestamp_as_prefix is False:
|
|
1668
|
+
backup_file(
|
|
1669
|
+
file_path='C:\\Users\\user1\\Downloads\\file.txt',
|
|
1670
|
+
backup_directory='C:\\Users\\user1\\Downloads\\backup',
|
|
1671
|
+
timestamp_as_prefix=False
|
|
1672
|
+
)
|
|
1673
|
+
|
|
1674
|
+
Backed up file will be moved to 'C:\\Users\\user1\\Downloads\\backup' with timestamp in the name.
|
|
1675
|
+
Final path will look like: 'C:\\Users\\user1\\Downloads\\backup\\file_20231003-120000-000000.txt'
|
|
1676
|
+
"""
|
|
1677
|
+
|
|
1678
|
+
if is_file_exists(file_path):
|
|
1679
|
+
timestamp: str = datetimes.TimeFormats().get_current_formatted_time_filename_stamp(True)
|
|
1680
|
+
file_name_no_extension = Path(file_path).stem
|
|
1681
|
+
file_extension = Path(file_path).suffix
|
|
1682
|
+
if timestamp_as_prefix:
|
|
1683
|
+
file_name: str = f"{timestamp}_{file_name_no_extension}{file_extension}"
|
|
1684
|
+
else:
|
|
1685
|
+
file_name: str = f"{file_name_no_extension}_{timestamp}{file_extension}"
|
|
1686
|
+
backup_file_path: str = str(Path(backup_directory) / file_name)
|
|
1687
|
+
rename_file(file_path, file_name)
|
|
1688
|
+
|
|
1689
|
+
return backup_file_path
|
|
1690
|
+
else:
|
|
1691
|
+
return None
|
|
1692
|
+
|
|
1693
|
+
|
|
1694
|
+
def find_file(file_name: str, directory_path: str):
|
|
1695
|
+
"""
|
|
1696
|
+
The function finds the file in the directory recursively.
|
|
1697
|
+
:param file_name: string, The name of the file to find.
|
|
1698
|
+
:param directory_path: string, The directory to search in.
|
|
1699
|
+
:return:
|
|
1700
|
+
"""
|
|
1701
|
+
for dir_path, dir_names, filenames in os.walk(directory_path):
|
|
1702
|
+
for filename in filenames:
|
|
1703
|
+
if filename == file_name:
|
|
1704
|
+
return os.path.join(dir_path, filename)
|
|
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)
|