Skip to content

Commit fe2c7ea

Browse files
Vibe coded gui, and changed default folder to something more sensible
1 parent 8ed89f5 commit fe2c7ea

4 files changed

Lines changed: 157 additions & 99 deletions

File tree

poetry.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ python = ">=3.10,<4.0"
1515
click = "^8.1.3"
1616
python-rclone = "^0.0.2"
1717
python-statemachine = "^2.0.0"
18-
flet = "^0.21.0"
18+
flet = "0.21.2"
1919

2020
[tool.poetry.group.dev.dependencies]
2121
pytest = "^9.0.2"

src/rclone_decrypt/decrypt.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import os
22
import re
3+
import sys
34
import tempfile
45

56
import rclone
67
from statemachine import State, StateMachine
78

8-
default_output_dir = "out"
9-
10-
# TODO(mitchellthompkins): This won't work on windows, check the rclone
11-
# documentation for the windows default location
12-
default_rclone_conf_dir = os.path.join(
13-
os.environ["HOME"], ".config", "rclone", "rclone.conf"
9+
default_output_dir = os.path.join(
10+
os.path.expanduser("~"), "Downloads", "rclone-decrypted"
1411
)
1512

13+
if sys.platform == "win32":
14+
# Windows default: %APPDATA%/rclone/rclone.conf
15+
default_rclone_conf_dir = os.path.join(
16+
os.environ.get("APPDATA", os.path.expanduser("~")),
17+
"rclone",
18+
"rclone.conf",
19+
)
20+
else:
21+
# macOS/Linux default: ~/.config/rclone/rclone.conf
22+
default_rclone_conf_dir = os.path.join(
23+
os.path.expanduser("~"), ".config", "rclone", "rclone.conf"
24+
)
25+
1626

1727
class ConfigFileError(Exception):
1828
def __init__(self, *args, **kwargs):

src/rclone_decrypt/gui.py

Lines changed: 135 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
TextField,
1919
colors,
2020
icons,
21+
Container,
2122
)
2223

2324
import rclone_decrypt.decrypt as decrypt
2425

25-
# Configure logging (similar to original)
26+
# Configure logging
2627
logging.basicConfig(
2728
filename="/tmp/rclone-decrypt-warning.log", level=logging.DEBUG
2829
)
@@ -41,16 +42,35 @@ def main(page: Page):
4142
config_file_path = decrypt.default_rclone_conf_dir
4243
output_dir_path = decrypt.default_output_dir
4344

44-
# UI Elements
45-
46-
# Config File Selection
47-
config_file_field = TextField(
48-
label="Config File",
49-
value=config_file_path,
50-
expand=True,
51-
read_only=True,
52-
)
45+
# --- Event Handlers & Pickers ---
5346

47+
def update_files_list():
48+
files_list_view.controls.clear()
49+
if not files_to_decrypt:
50+
files_list_view.controls.append(Text("No files selected."))
51+
else:
52+
for f in files_to_decrypt:
53+
files_list_view.controls.append(
54+
Row(
55+
controls=[
56+
IconButton(
57+
icon=icons.CLOSE,
58+
icon_color=colors.GREY_700,
59+
on_click=lambda e, path=f: remove_file(path),
60+
tooltip="Remove from list",
61+
),
62+
Text(f, expand=True),
63+
]
64+
)
65+
)
66+
page.update()
67+
68+
def remove_file(path_to_remove):
69+
if path_to_remove in files_to_decrypt:
70+
files_to_decrypt.remove(path_to_remove)
71+
update_files_list()
72+
73+
# 1. Config Picker
5474
def pick_config_result(e: FilePickerResultEvent):
5575
nonlocal config_file_path
5676
if e.files:
@@ -61,6 +81,52 @@ def pick_config_result(e: FilePickerResultEvent):
6181
config_picker = FilePicker(on_result=pick_config_result)
6282
page.overlay.append(config_picker)
6383

84+
# 2. Output Picker
85+
def pick_output_result(e: FilePickerResultEvent):
86+
nonlocal output_dir_path
87+
if e.path:
88+
output_dir_path = e.path
89+
output_dir_field.value = output_dir_path
90+
page.update()
91+
92+
output_picker = FilePicker(on_result=pick_output_result)
93+
page.overlay.append(output_picker)
94+
95+
# 3. Add Folder Picker
96+
def add_folder_result(e: FilePickerResultEvent):
97+
if e.path:
98+
# Walk the directory and add all files
99+
for root, dirs, files in os.walk(e.path):
100+
for file in files:
101+
full_path = os.path.join(root, file)
102+
if full_path not in files_to_decrypt:
103+
files_to_decrypt.append(full_path)
104+
update_files_list()
105+
106+
add_folder_picker = FilePicker(on_result=add_folder_result)
107+
page.overlay.append(add_folder_picker)
108+
109+
# 4. Add Files Picker (Multiple)
110+
def add_files_result(e: FilePickerResultEvent):
111+
if e.files:
112+
for f in e.files:
113+
if f.path not in files_to_decrypt:
114+
files_to_decrypt.append(f.path)
115+
update_files_list()
116+
117+
add_files_picker = FilePicker(on_result=add_files_result)
118+
page.overlay.append(add_files_picker)
119+
120+
# --- UI Components ---
121+
122+
# Config Row
123+
config_file_field = TextField(
124+
label="Config File",
125+
value=config_file_path,
126+
expand=True,
127+
read_only=True,
128+
)
129+
64130
config_row = Row(
65131
controls=[
66132
config_file_field,
@@ -74,24 +140,14 @@ def pick_config_result(e: FilePickerResultEvent):
74140
],
75141
)
76142

77-
# Output Directory Selection
143+
# Output Row
78144
output_dir_field = TextField(
79145
label="Output Directory",
80146
value=output_dir_path,
81147
expand=True,
82148
read_only=True,
83149
)
84150

85-
def pick_output_result(e: FilePickerResultEvent):
86-
nonlocal output_dir_path
87-
if e.path:
88-
output_dir_path = e.path
89-
output_dir_field.value = output_dir_path
90-
page.update()
91-
92-
output_picker = FilePicker(on_result=pick_output_result)
93-
page.overlay.append(output_picker)
94-
95151
output_row = Row(
96152
controls=[
97153
output_dir_field,
@@ -103,54 +159,71 @@ def pick_output_result(e: FilePickerResultEvent):
103159
],
104160
)
105161

106-
# Files List
107-
files_list_view = ListView(expand=True, spacing=10, padding=10)
108-
109-
def remove_file(path_to_remove):
110-
if path_to_remove in files_to_decrypt:
111-
files_to_decrypt.remove(path_to_remove)
112-
update_files_list()
162+
# Files List Area
163+
files_list_view = ListView(expand=True, spacing=5, padding=5)
164+
165+
# Initialize list
166+
update_files_list()
113167

114-
def update_files_list():
115-
files_list_view.controls.clear()
116-
for f in files_to_decrypt:
117-
files_list_view.controls.append(
118-
Row(
119-
controls=[
120-
Text(f, expand=True),
121-
IconButton(
122-
icon=icons.DELETE,
123-
icon_color=colors.RED,
124-
on_click=lambda e, path=f: remove_file(path),
125-
),
126-
]
127-
)
168+
files_area = Column(
169+
controls=[
170+
Row(
171+
controls=[
172+
Text("Files to Decrypt:", style=ft.TextThemeStyle.TITLE_MEDIUM, expand=True),
173+
Row(
174+
controls=[
175+
ElevatedButton(
176+
"Add Files",
177+
icon=icons.ADD,
178+
on_click=lambda _: add_files_picker.pick_files(
179+
allow_multiple=True,
180+
dialog_title="Select Files to Decrypt"
181+
),
182+
),
183+
ElevatedButton(
184+
"Add Folder",
185+
icon=icons.CREATE_NEW_FOLDER,
186+
on_click=lambda _: add_folder_picker.get_directory_path(
187+
dialog_title="Select Folder to Decrypt"
188+
),
189+
),
190+
],
191+
spacing=10,
192+
)
193+
],
194+
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
195+
),
196+
Container(
197+
content=files_list_view,
198+
border=ft.border.all(1, colors.OUTLINE),
199+
border_radius=5,
200+
height=200,
128201
)
129-
page.update()
130-
131-
# Drag and Drop Handler
132-
def file_picker_result(e: FilePickerResultEvent):
133-
if e.files:
134-
for f in e.files:
135-
if f.path not in files_to_decrypt:
136-
files_to_decrypt.append(f.path)
137-
update_files_list()
138-
139-
# Native OS Drag and Drop (if supported by Flet on the platform)
140-
# Note: Flet's `on_file_drop` handles this.
202+
]
203+
)
141204

142205
# Decrypt Action
143206
status_text = Text("")
144207

145208
def decrypt_click(e):
146209
status_text.value = "Decrypting..."
210+
status_text.color = colors.BLUE
147211
page.update()
148212

149213
try:
214+
if not files_to_decrypt:
215+
status_text.value = "No files to decrypt!"
216+
status_text.color = colors.RED
217+
page.update()
218+
return
219+
150220
for f in files_to_decrypt:
151-
# Strip potential extra quotes if dragged/dropped might add them
152-
# though Flet usually handles paths cleanly.
153221
clean_path = f.strip('"\'')
222+
# Verify file exists
223+
if not os.path.exists(clean_path):
224+
print(f"Skipping missing file: {clean_path}")
225+
continue
226+
154227
decrypt.decrypt(clean_path, config_file_path, output_dir_path)
155228

156229
status_text.value = "Decryption Complete!"
@@ -160,7 +233,6 @@ def decrypt_click(e):
160233
status_text.value = err_msg
161234
status_text.color = colors.RED
162235

163-
# Log error
164236
err_logger = logging.getLogger(__name__)
165237
now = datetime.now()
166238
trace = traceback.format_exc()
@@ -170,9 +242,8 @@ def decrypt_click(e):
170242

171243
page.update()
172244

173-
174245
decrypt_button = ElevatedButton(
175-
"Decrypt Files",
246+
"Decrypt All Files",
176247
icon=icons.LOCK_OPEN,
177248
on_click=decrypt_click,
178249
style=ft.ButtonStyle(
@@ -181,42 +252,19 @@ def decrypt_click(e):
181252
)
182253
)
183254

184-
# Layout
255+
# Main Layout
185256
page.add(
186257
Text("Rclone Decrypt", style=ft.TextThemeStyle.HEADLINE_MEDIUM),
187258
config_row,
188259
output_row,
189-
Text("Files to Decrypt (Drag and drop files here):"),
190-
ft.Container(
191-
content=files_list_view,
192-
border=ft.border.all(1, colors.OUTLINE),
193-
border_radius=5,
194-
height=200, # Fixed height for the list area
195-
),
260+
ft.Divider(),
261+
files_area,
262+
ft.Divider(),
196263
Row([decrypt_button], alignment=ft.MainAxisAlignment.CENTER),
197264
status_text,
198265
)
199266

200-
# Handle file drop
201-
def on_drop(e: ft.FilePickerResultEvent):
202-
# Flet on_file_drop event returns a FilePickerResultEvent-like object
203-
# but strictly speaking it's a specific event type, however we might need to parse it.
204-
# Actually, page.on_file_drop passes a FileDropEvent
205-
pass
206-
207-
# Since Flet 0.21.0, page.on_file_drop is the way for drag and drop
208-
def page_on_drop(e: ft.FileDropEvent):
209-
# e.files is a list of FileDropEventFile
210-
for f in e.files:
211-
path = f.path
212-
if path not in files_to_decrypt:
213-
files_to_decrypt.append(path)
214-
update_files_list()
215-
216-
page.on_file_drop = page_on_drop
217-
218-
219267
ft.app(target=main)
220268

221269
if __name__ == "__main__":
222-
start_gui()
270+
start_gui()

0 commit comments

Comments
 (0)