TonieToolbox 0.5.1__py3-none-any.whl → 0.6.0a1__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.
- TonieToolbox/__init__.py +1 -1
- TonieToolbox/__main__.py +85 -84
- TonieToolbox/artwork.py +12 -7
- TonieToolbox/audio_conversion.py +31 -28
- TonieToolbox/config.py +1 -0
- TonieToolbox/constants.py +93 -9
- TonieToolbox/filename_generator.py +6 -8
- TonieToolbox/integration.py +20 -0
- TonieToolbox/integration_macos.py +428 -0
- TonieToolbox/integration_ubuntu.py +1 -0
- TonieToolbox/integration_windows.py +404 -0
- TonieToolbox/logger.py +8 -10
- TonieToolbox/media_tags.py +17 -97
- TonieToolbox/ogg_page.py +39 -39
- TonieToolbox/opus_packet.py +13 -13
- TonieToolbox/recursive_processor.py +22 -22
- TonieToolbox/tags.py +3 -4
- TonieToolbox/teddycloud.py +50 -50
- TonieToolbox/tonie_analysis.py +24 -23
- TonieToolbox/tonie_file.py +71 -44
- TonieToolbox/tonies_json.py +69 -66
- TonieToolbox/version_handler.py +12 -15
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a1.dist-info}/METADATA +2 -2
- tonietoolbox-0.6.0a1.dist-info/RECORD +31 -0
- tonietoolbox-0.5.1.dist-info/RECORD +0 -26
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a1.dist-info}/WHEEL +0 -0
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a1.dist-info}/entry_points.txt +0 -0
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a1.dist-info}/licenses/LICENSE.md +0 -0
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,428 @@
|
|
1
|
+
# filepath: d:\Repository\TonieToolbox\TonieToolbox\integration_macos.py
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import json
|
5
|
+
import plistlib
|
6
|
+
import subprocess
|
7
|
+
from pathlib import Path
|
8
|
+
from .constants import SUPPORTED_EXTENSIONS
|
9
|
+
from .logger import get_logger
|
10
|
+
|
11
|
+
logger = get_logger('integration_macos')
|
12
|
+
|
13
|
+
class MacOSContextMenuIntegration:
|
14
|
+
"""
|
15
|
+
Class to generate macOS Quick Actions for TonieToolbox integration.
|
16
|
+
Creates Quick Actions (Services) for supported audio files, .taf files, and folders.
|
17
|
+
"""
|
18
|
+
def __init__(self):
|
19
|
+
# Find the installed command-line tool path
|
20
|
+
self.exe_path = os.path.join(sys.prefix, 'bin', 'tonietoolbox')
|
21
|
+
self.output_dir = os.path.join(os.path.expanduser('~'), '.tonietoolbox')
|
22
|
+
self.services_dir = os.path.join(os.path.expanduser('~'), 'Library', 'Services')
|
23
|
+
self.icon_path = os.path.join(self.output_dir, 'icon.png')
|
24
|
+
os.makedirs(self.output_dir, exist_ok=True)
|
25
|
+
|
26
|
+
# Error handling and success messages for shell scripts
|
27
|
+
self.error_handling = 'if [ $? -ne 0 ]; then\n echo "Error: Command failed with error code $?"\n read -p "Press any key to close this window..." key\n exit 1\nfi'
|
28
|
+
self.success_handling = 'echo "Command completed successfully"\nsleep 2'
|
29
|
+
|
30
|
+
# Load configuration
|
31
|
+
self.config = self._load_config()
|
32
|
+
|
33
|
+
# Ensure these attributes always exist
|
34
|
+
self.upload_url = ''
|
35
|
+
self.log_level = self.config.get('log_level', 'SILENT')
|
36
|
+
self.log_to_file = self.config.get('log_to_file', False)
|
37
|
+
self.basic_authentication_cmd = ''
|
38
|
+
self.client_cert_cmd = ''
|
39
|
+
self.upload_enabled = self._setup_upload()
|
40
|
+
|
41
|
+
logger.debug(f"Upload enabled: {self.upload_enabled}")
|
42
|
+
logger.debug(f"Upload URL: {self.upload_url}")
|
43
|
+
logger.debug(f"Authentication: {'Basic Authentication' if self.basic_authentication else ('None' if self.none_authentication else ('Client Cert' if self.client_cert_authentication else 'Unknown'))}")
|
44
|
+
|
45
|
+
self._setup_commands()
|
46
|
+
|
47
|
+
def _build_cmd(self, base_args, file_placeholder='$1', output_to_source=True, use_upload=False, use_artwork=False, use_json=False, use_compare=False, use_info=False, is_recursive=False, is_split=False, is_folder=False, keep_open=False, log_to_file=False):
|
48
|
+
"""Dynamically build command strings for quick actions."""
|
49
|
+
exe = self.exe_path
|
50
|
+
cmd = '#!/bin/bash\n\n'
|
51
|
+
|
52
|
+
# Add a description of what's being executed
|
53
|
+
cmd += 'echo "Running TonieToolbox'
|
54
|
+
if use_info:
|
55
|
+
cmd += ' info'
|
56
|
+
elif is_split:
|
57
|
+
cmd += ' split'
|
58
|
+
elif use_compare:
|
59
|
+
cmd += ' compare'
|
60
|
+
elif is_recursive:
|
61
|
+
cmd += ' recursive folder convert'
|
62
|
+
elif is_folder:
|
63
|
+
cmd += ' folder convert'
|
64
|
+
elif use_upload and use_artwork and use_json:
|
65
|
+
cmd += ' convert, upload, artwork and JSON'
|
66
|
+
elif use_upload and use_artwork:
|
67
|
+
cmd += ' convert, upload and artwork'
|
68
|
+
elif use_upload:
|
69
|
+
cmd += ' convert and upload'
|
70
|
+
else:
|
71
|
+
cmd += ' convert'
|
72
|
+
cmd += ' command..."\n\n'
|
73
|
+
|
74
|
+
# Build the actual command
|
75
|
+
cmd_line = f'"{exe}" {base_args}'
|
76
|
+
if log_to_file:
|
77
|
+
cmd_line += ' --log-file'
|
78
|
+
if is_recursive:
|
79
|
+
cmd_line += ' --recursive'
|
80
|
+
if output_to_source:
|
81
|
+
cmd_line += ' --output-to-source'
|
82
|
+
if use_info:
|
83
|
+
cmd_line += ' --info'
|
84
|
+
if is_split:
|
85
|
+
cmd_line += ' --split'
|
86
|
+
if use_compare:
|
87
|
+
cmd_line += ' --compare "$1" "$2"'
|
88
|
+
else:
|
89
|
+
cmd_line += f' "{file_placeholder}"'
|
90
|
+
if use_upload:
|
91
|
+
cmd_line += f' --upload "{self.upload_url}"'
|
92
|
+
if self.basic_authentication_cmd:
|
93
|
+
cmd_line += f' {self.basic_authentication_cmd}'
|
94
|
+
elif self.client_cert_cmd:
|
95
|
+
cmd_line += f' {self.client_cert_cmd}'
|
96
|
+
if getattr(self, "ignore_ssl_verify", False):
|
97
|
+
cmd_line += ' --ignore-ssl-verify'
|
98
|
+
if use_artwork:
|
99
|
+
cmd_line += ' --include-artwork'
|
100
|
+
if use_json:
|
101
|
+
cmd_line += ' --create-custom-json'
|
102
|
+
|
103
|
+
# Add the command to the script
|
104
|
+
cmd += f'{cmd_line}\n\n'
|
105
|
+
|
106
|
+
# Add error and success handling
|
107
|
+
cmd += f'{self.error_handling}\n\n'
|
108
|
+
if use_info or use_compare or keep_open:
|
109
|
+
cmd += 'echo ""\nread -p "Press any key to close this window..." key\n'
|
110
|
+
else:
|
111
|
+
cmd += f'{self.success_handling}\n'
|
112
|
+
|
113
|
+
return cmd
|
114
|
+
|
115
|
+
def _get_log_level_arg(self):
|
116
|
+
"""Return the correct log level argument for TonieToolbox CLI based on self.log_level."""
|
117
|
+
level = str(self.log_level).strip().upper()
|
118
|
+
if level == 'DEBUG':
|
119
|
+
return '--debug'
|
120
|
+
elif level == 'INFO':
|
121
|
+
return '--info'
|
122
|
+
return '--silent'
|
123
|
+
|
124
|
+
def _setup_commands(self):
|
125
|
+
"""Set up all command strings for quick actions dynamically."""
|
126
|
+
log_level_arg = self._get_log_level_arg()
|
127
|
+
|
128
|
+
# Audio file commands
|
129
|
+
self.convert_cmd = self._build_cmd(f'{log_level_arg}', log_to_file=self.log_to_file)
|
130
|
+
self.upload_cmd = self._build_cmd(f'{log_level_arg}', use_upload=True, log_to_file=self.log_to_file)
|
131
|
+
self.upload_artwork_cmd = self._build_cmd(f'{log_level_arg}', use_upload=True, use_artwork=True, log_to_file=self.log_to_file)
|
132
|
+
self.upload_artwork_json_cmd = self._build_cmd(f'{log_level_arg}', use_upload=True, use_artwork=True, use_json=True, log_to_file=self.log_to_file)
|
133
|
+
|
134
|
+
# .taf file commands
|
135
|
+
self.show_info_cmd = self._build_cmd(log_level_arg, use_info=True, keep_open=True, log_to_file=self.log_to_file)
|
136
|
+
self.extract_opus_cmd = self._build_cmd(log_level_arg, is_split=True, log_to_file=self.log_to_file)
|
137
|
+
self.upload_taf_cmd = self._build_cmd(log_level_arg, use_upload=True, log_to_file=self.log_to_file)
|
138
|
+
self.upload_taf_artwork_cmd = self._build_cmd(log_level_arg, use_upload=True, use_artwork=True, log_to_file=self.log_to_file)
|
139
|
+
self.upload_taf_artwork_json_cmd = self._build_cmd(log_level_arg, use_upload=True, use_artwork=True, use_json=True, log_to_file=self.log_to_file)
|
140
|
+
self.compare_taf_cmd = self._build_cmd(log_level_arg, use_compare=True, keep_open=True, log_to_file=self.log_to_file)
|
141
|
+
|
142
|
+
# Folder commands
|
143
|
+
self.convert_folder_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, log_to_file=self.log_to_file)
|
144
|
+
self.upload_folder_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, use_upload=True, log_to_file=self.log_to_file)
|
145
|
+
self.upload_folder_artwork_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, use_upload=True, use_artwork=True, log_to_file=self.log_to_file)
|
146
|
+
self.upload_folder_artwork_json_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, use_upload=True, use_artwork=True, use_json=True, log_to_file=self.log_to_file)
|
147
|
+
|
148
|
+
def _load_config(self):
|
149
|
+
"""Load configuration settings from config.json"""
|
150
|
+
config_path = os.path.join(self.output_dir, 'config.json')
|
151
|
+
if not os.path.exists(config_path):
|
152
|
+
logger.debug(f"Configuration file not found: {config_path}")
|
153
|
+
return {}
|
154
|
+
|
155
|
+
try:
|
156
|
+
with open(config_path, 'r') as f:
|
157
|
+
config = json.loads(f.read())
|
158
|
+
return config
|
159
|
+
except (json.JSONDecodeError, IOError) as e:
|
160
|
+
logger.debug(f"Error loading config: {e}")
|
161
|
+
return {}
|
162
|
+
|
163
|
+
def _setup_upload(self):
|
164
|
+
"""Set up upload functionality based on config.json settings"""
|
165
|
+
# Always initialize authentication flags
|
166
|
+
self.basic_authentication = False
|
167
|
+
self.client_cert_authentication = False
|
168
|
+
self.none_authentication = False
|
169
|
+
|
170
|
+
config = self.config
|
171
|
+
try:
|
172
|
+
upload_config = config.get('upload', {})
|
173
|
+
self.upload_urls = upload_config.get('url', [])
|
174
|
+
self.ignore_ssl_verify = upload_config.get('ignore_ssl_verify', False)
|
175
|
+
self.username = upload_config.get('username', '')
|
176
|
+
self.password = upload_config.get('password', '')
|
177
|
+
self.basic_authentication_cmd = ''
|
178
|
+
self.client_cert_cmd = ''
|
179
|
+
|
180
|
+
if self.username and self.password:
|
181
|
+
self.basic_authentication_cmd = f'--username {self.username} --password {self.password}'
|
182
|
+
self.basic_authentication = True
|
183
|
+
|
184
|
+
self.client_cert_path = upload_config.get('client_cert_path', '')
|
185
|
+
self.client_cert_key_path = upload_config.get('client_cert_key_path', '')
|
186
|
+
if self.client_cert_path and self.client_cert_key_path:
|
187
|
+
self.client_cert_cmd = f'--client-cert {self.client_cert_path} --client-cert-key {self.client_cert_key_path}'
|
188
|
+
self.client_cert_authentication = True
|
189
|
+
|
190
|
+
if self.client_cert_authentication and self.basic_authentication:
|
191
|
+
logger.warning("Both client certificate and basic authentication are set. Only one can be used.")
|
192
|
+
return False
|
193
|
+
|
194
|
+
self.upload_url = self.upload_urls[0] if self.upload_urls else ''
|
195
|
+
if not self.client_cert_authentication and not self.basic_authentication and self.upload_url:
|
196
|
+
self.none_authentication = True
|
197
|
+
|
198
|
+
return bool(self.upload_url)
|
199
|
+
except Exception as e:
|
200
|
+
logger.debug(f"Unexpected error while loading configuration: {e}")
|
201
|
+
return False
|
202
|
+
|
203
|
+
def _create_quick_action(self, name, command, file_types=None, directory_based=False):
|
204
|
+
"""Create a macOS Quick Action (Service) with the given name and command."""
|
205
|
+
# Create Quick Action directory
|
206
|
+
action_dir = os.path.join(self.services_dir, f"{name}.workflow")
|
207
|
+
os.makedirs(action_dir, exist_ok=True)
|
208
|
+
|
209
|
+
# Create Contents directory
|
210
|
+
contents_dir = os.path.join(action_dir, "Contents")
|
211
|
+
os.makedirs(contents_dir, exist_ok=True)
|
212
|
+
|
213
|
+
# Create document.wflow file with plist content
|
214
|
+
document_path = os.path.join(contents_dir, "document.wflow")
|
215
|
+
|
216
|
+
# Create Info.plist
|
217
|
+
info_plist = {
|
218
|
+
"NSServices": [
|
219
|
+
{
|
220
|
+
"NSMenuItem": {
|
221
|
+
"default": name
|
222
|
+
},
|
223
|
+
"NSMessage": "runWorkflowAsService",
|
224
|
+
"NSRequiredContext": {
|
225
|
+
"NSApplicationIdentifier": "com.apple.finder"
|
226
|
+
},
|
227
|
+
"NSSendFileTypes": file_types if file_types else [],
|
228
|
+
"NSSendTypes": ["NSFilenamesPboardType"] if directory_based else []
|
229
|
+
}
|
230
|
+
]
|
231
|
+
}
|
232
|
+
|
233
|
+
info_path = os.path.join(contents_dir, "Info.plist")
|
234
|
+
with open(info_path, "wb") as f:
|
235
|
+
plistlib.dump(info_plist, f)
|
236
|
+
|
237
|
+
# Create script file
|
238
|
+
script_dir = os.path.join(contents_dir, "MacOS")
|
239
|
+
os.makedirs(script_dir, exist_ok=True)
|
240
|
+
script_path = os.path.join(script_dir, "script")
|
241
|
+
|
242
|
+
with open(script_path, "w") as f:
|
243
|
+
f.write(command)
|
244
|
+
|
245
|
+
# Make the script executable
|
246
|
+
os.chmod(script_path, 0o755)
|
247
|
+
|
248
|
+
# Create document.wflow file with a basic workflow definition
|
249
|
+
workflow = {
|
250
|
+
"AMApplication": "Automator",
|
251
|
+
"AMCanShowSelectedItemsWhenRun": False,
|
252
|
+
"AMCanShowWhenRun": True,
|
253
|
+
"AMDockBadgeLabel": "",
|
254
|
+
"AMDockBadgeStyle": "badge",
|
255
|
+
"AMName": name,
|
256
|
+
"AMRootElement": {
|
257
|
+
"actions": [
|
258
|
+
{
|
259
|
+
"action": "run-shell-script",
|
260
|
+
"parameters": {
|
261
|
+
"shell": "/bin/bash",
|
262
|
+
"script": command,
|
263
|
+
"input": "as arguments"
|
264
|
+
}
|
265
|
+
}
|
266
|
+
],
|
267
|
+
"class": "workflow",
|
268
|
+
"connections": {},
|
269
|
+
"id": "workflow-element",
|
270
|
+
"title": name
|
271
|
+
},
|
272
|
+
"AMWorkflowSchemeVersion": 2.0,
|
273
|
+
}
|
274
|
+
|
275
|
+
with open(document_path, "wb") as f:
|
276
|
+
plistlib.dump(workflow, f)
|
277
|
+
|
278
|
+
return action_dir
|
279
|
+
|
280
|
+
def _generate_audio_extension_actions(self):
|
281
|
+
"""Generate Quick Actions for supported audio file extensions."""
|
282
|
+
extensions = [ext.lower().lstrip('.') for ext in SUPPORTED_EXTENSIONS]
|
283
|
+
|
284
|
+
# Create audio file actions
|
285
|
+
self._create_quick_action(
|
286
|
+
"TonieToolbox - Convert to TAF",
|
287
|
+
self.convert_cmd,
|
288
|
+
file_types=extensions
|
289
|
+
)
|
290
|
+
|
291
|
+
if self.upload_enabled:
|
292
|
+
self._create_quick_action(
|
293
|
+
"TonieToolbox - Convert and Upload",
|
294
|
+
self.upload_cmd,
|
295
|
+
file_types=extensions
|
296
|
+
)
|
297
|
+
|
298
|
+
self._create_quick_action(
|
299
|
+
"TonieToolbox - Convert, Upload with Artwork",
|
300
|
+
self.upload_artwork_cmd,
|
301
|
+
file_types=extensions
|
302
|
+
)
|
303
|
+
|
304
|
+
self._create_quick_action(
|
305
|
+
"TonieToolbox - Convert, Upload with Artwork and JSON",
|
306
|
+
self.upload_artwork_json_cmd,
|
307
|
+
file_types=extensions
|
308
|
+
)
|
309
|
+
|
310
|
+
def _generate_taf_file_actions(self):
|
311
|
+
"""Generate Quick Actions for .taf files."""
|
312
|
+
self._create_quick_action(
|
313
|
+
"TonieToolbox - Show Info",
|
314
|
+
self.show_info_cmd,
|
315
|
+
file_types=["taf"]
|
316
|
+
)
|
317
|
+
|
318
|
+
self._create_quick_action(
|
319
|
+
"TonieToolbox - Extract Opus Tracks",
|
320
|
+
self.extract_opus_cmd,
|
321
|
+
file_types=["taf"]
|
322
|
+
)
|
323
|
+
|
324
|
+
if self.upload_enabled:
|
325
|
+
self._create_quick_action(
|
326
|
+
"TonieToolbox - Upload",
|
327
|
+
self.upload_taf_cmd,
|
328
|
+
file_types=["taf"]
|
329
|
+
)
|
330
|
+
|
331
|
+
self._create_quick_action(
|
332
|
+
"TonieToolbox - Upload with Artwork",
|
333
|
+
self.upload_taf_artwork_cmd,
|
334
|
+
file_types=["taf"]
|
335
|
+
)
|
336
|
+
|
337
|
+
self._create_quick_action(
|
338
|
+
"TonieToolbox - Upload with Artwork and JSON",
|
339
|
+
self.upload_taf_artwork_json_cmd,
|
340
|
+
file_types=["taf"]
|
341
|
+
)
|
342
|
+
|
343
|
+
self._create_quick_action(
|
344
|
+
"TonieToolbox - Compare with another TAF file",
|
345
|
+
self.compare_taf_cmd,
|
346
|
+
file_types=["taf"]
|
347
|
+
)
|
348
|
+
|
349
|
+
def _generate_folder_actions(self):
|
350
|
+
"""Generate Quick Actions for folders."""
|
351
|
+
self._create_quick_action(
|
352
|
+
"TonieToolbox - Convert Folder to TAF (recursive)",
|
353
|
+
self.convert_folder_cmd,
|
354
|
+
directory_based=True
|
355
|
+
)
|
356
|
+
|
357
|
+
if self.upload_enabled:
|
358
|
+
self._create_quick_action(
|
359
|
+
"TonieToolbox - Convert Folder and Upload (recursive)",
|
360
|
+
self.upload_folder_cmd,
|
361
|
+
directory_based=True
|
362
|
+
)
|
363
|
+
|
364
|
+
self._create_quick_action(
|
365
|
+
"TonieToolbox - Convert Folder, Upload with Artwork (recursive)",
|
366
|
+
self.upload_folder_artwork_cmd,
|
367
|
+
directory_based=True
|
368
|
+
)
|
369
|
+
|
370
|
+
self._create_quick_action(
|
371
|
+
"TonieToolbox - Convert Folder, Upload with Artwork and JSON (recursive)",
|
372
|
+
self.upload_folder_artwork_json_cmd,
|
373
|
+
directory_based=True
|
374
|
+
)
|
375
|
+
|
376
|
+
def install_quick_actions(self):
|
377
|
+
"""Install all Quick Actions."""
|
378
|
+
# Ensure Services directory exists
|
379
|
+
os.makedirs(self.services_dir, exist_ok=True)
|
380
|
+
|
381
|
+
# Check if the icon exists, copy default if needed
|
382
|
+
if not os.path.exists(self.icon_path):
|
383
|
+
# Include code to extract icon from resources
|
384
|
+
logger.debug(f"Icon not found at {self.icon_path}, using default")
|
385
|
+
|
386
|
+
# Generate Quick Actions for different file types
|
387
|
+
self._generate_audio_extension_actions()
|
388
|
+
self._generate_taf_file_actions()
|
389
|
+
self._generate_folder_actions()
|
390
|
+
|
391
|
+
# Refresh the Services menu by restarting the Finder
|
392
|
+
subprocess.run(["killall", "-HUP", "Finder"], check=False)
|
393
|
+
|
394
|
+
print("TonieToolbox Quick Actions installed successfully.")
|
395
|
+
print("You'll find them in the Services menu when right-clicking on audio files, TAF files, or folders.")
|
396
|
+
|
397
|
+
def uninstall_quick_actions(self):
|
398
|
+
"""Uninstall all TonieToolbox Quick Actions."""
|
399
|
+
# Find and remove all TonieToolbox Quick Actions
|
400
|
+
for item in os.listdir(self.services_dir):
|
401
|
+
if item.startswith("TonieToolbox - ") and item.endswith(".workflow"):
|
402
|
+
action_path = os.path.join(self.services_dir, item)
|
403
|
+
try:
|
404
|
+
subprocess.run(["rm", "-rf", action_path], check=True)
|
405
|
+
print(f"Removed: {item}")
|
406
|
+
except subprocess.CalledProcessError:
|
407
|
+
print(f"Failed to remove: {item}")
|
408
|
+
|
409
|
+
# Refresh the Services menu
|
410
|
+
subprocess.run(["killall", "-HUP", "Finder"], check=False)
|
411
|
+
|
412
|
+
print("TonieToolbox Quick Actions uninstalled successfully.")
|
413
|
+
|
414
|
+
@classmethod
|
415
|
+
def install(cls):
|
416
|
+
"""
|
417
|
+
Generate Quick Actions and install them.
|
418
|
+
"""
|
419
|
+
instance = cls()
|
420
|
+
instance.install_quick_actions()
|
421
|
+
|
422
|
+
@classmethod
|
423
|
+
def uninstall(cls):
|
424
|
+
"""
|
425
|
+
Uninstall all TonieToolbox Quick Actions.
|
426
|
+
"""
|
427
|
+
instance = cls()
|
428
|
+
instance.uninstall_quick_actions()
|
@@ -0,0 +1 @@
|
|
1
|
+
# TODO: Add integration_ubuntu.py
|