Skip to content

Commit 7ae0b84

Browse files
committed
The file browser will now retrieve your home directory when you use it on a server for the first time.
1 parent 660e33c commit 7ae0b84

File tree

4 files changed

+72
-42
lines changed

4 files changed

+72
-42
lines changed

dialogs.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from sftp_helpers import upload_item, download_item, delete_item
1111

1212
def human_readable_size(size_bytes):
13-
"""Converts a size in bytes to a human-readable format."""
1413
if size_bytes < 1024:
1514
return f"{size_bytes} B"
1615
size_name = ("B", "KB", "MB", "GB", "TB")
@@ -23,7 +22,6 @@ def human_readable_size(size_bytes):
2322
except (ValueError, IndexError):
2423
return str(size_bytes)
2524

26-
2725
class AddServerDialog(wx.Dialog):
2826
def __init__(self, parent, title="Add SSH Server", server_to_edit=None):
2927
super(AddServerDialog, self).__init__(parent, title=title, size=(400, 420))
@@ -154,12 +152,16 @@ def get_data(self):
154152
return data
155153

156154
class FileBrowserDialog(wx.Dialog):
157-
def __init__(self, parent, sftp_client, edit_callback):
155+
def __init__(self, parent, sftp_client, edit_callback, initial_path=None):
158156
super(FileBrowserDialog, self).__init__(parent, title="SFTP File Browser", size=(600, 480))
159157

160-
self.sftp, self.current_path, self.edit_callback = sftp_client, sftp_client.getcwd() or "/", edit_callback
161-
self.progress_dialog, self.copy_temp_dir = None, None
162-
158+
self.sftp = sftp_client
159+
# --- FIX: The initial path is now guaranteed to be valid if provided. ---
160+
self.current_path = initial_path or "/"
161+
self.edit_callback = edit_callback
162+
self.progress_dialog = None
163+
self.copy_temp_dir = None
164+
163165
panel = wx.Panel(self)
164166
self.vbox = wx.BoxSizer(wx.VERTICAL)
165167
self.path_text = wx.StaticText(panel, label=self.current_path)
@@ -169,7 +171,6 @@ def __init__(self, parent, sftp_client, edit_callback):
169171
self.file_list.InsertColumn(1, "Size", width=100)
170172
self.file_list.InsertColumn(2, "Type", width=100)
171173
self.vbox.Add(self.file_list, 1, wx.ALL|wx.EXPAND, 5)
172-
173174
hbox_buttons = wx.BoxSizer(wx.HORIZONTAL)
174175
self.upload_button = wx.Button(panel, label="&Upload")
175176
self.new_button = wx.Button(panel, label="&New...")
@@ -178,7 +179,6 @@ def __init__(self, parent, sftp_client, edit_callback):
178179
self.edit_button = wx.Button(panel, label="&Edit")
179180
self.delete_button = wx.Button(panel, label="&Delete")
180181
close_button = wx.Button(panel, label="&Close", id=wx.ID_CANCEL)
181-
182182
hbox_buttons.Add(self.upload_button)
183183
hbox_buttons.Add(self.new_button, flag=wx.LEFT, border=5)
184184
hbox_buttons.Add(self.download_button, flag=wx.LEFT, border=5)
@@ -188,11 +188,9 @@ def __init__(self, parent, sftp_client, edit_callback):
188188
hbox_buttons.AddStretchSpacer()
189189
hbox_buttons.Add(close_button)
190190
self.vbox.Add(hbox_buttons, 0, wx.ALL|wx.EXPAND, 5)
191-
192191
self.status_text = wx.StaticText(panel, label=" ")
193192
self.vbox.Add(self.status_text, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, 5)
194193
panel.SetSizer(self.vbox)
195-
196194
self.Bind(wx.EVT_ACTIVATE, self.on_activate)
197195
self.file_list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_item_activated)
198196
self.file_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_selection_changed)
@@ -205,15 +203,16 @@ def __init__(self, parent, sftp_client, edit_callback):
205203
self.copy_button.Bind(wx.EVT_BUTTON, self.on_copy)
206204
self.delete_button.Bind(wx.EVT_BUTTON, self.on_delete)
207205
self.Bind(wx.EVT_CLOSE, self.on_close)
208-
209206
theme.apply_dark_theme(self)
210207
self.populate_files()
211208

209+
def get_current_path(self):
210+
return self.current_path
211+
212212
def on_activate(self, event):
213-
if event.GetActive():
214-
self.populate_files()
213+
if event.GetActive(): self.populate_files()
215214
event.Skip()
216-
215+
217216
def populate_files(self):
218217
self.path_text.SetLabel(f"Path: {self.current_path}")
219218
self.file_list.DeleteAllItems()
@@ -231,6 +230,8 @@ def populate_files(self):
231230
self.file_list.SetItem(index, 2, "File")
232231
except Exception as e:
233232
wx.MessageBox(f"Could not list directory: {e}", "SFTP Error", wx.ICON_ERROR)
233+
# If the path is invalid, fall back to root
234+
self.current_path = "/"
234235
self.on_selection_changed(None)
235236

236237
def on_close(self, event):
@@ -419,7 +420,6 @@ def on_edit_button(self, event):
419420
remote_paths = self.get_selected_remote_paths()
420421
if remote_paths:
421422
self.edit_callback(remote_paths[0])
422-
# self.Close() # This is correctly removed now.
423423

424424
def on_paste_upload(self, event):
425425
data = wx.FileDataObject()

teatype.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# --- START OF FILE teatype.py ---
2-
31
import wx
42
import json
53
import os
@@ -16,69 +14,69 @@
1614

1715
class MainFrame(wx.Frame, SettingsMenuMixin):
1816
def __init__(self):
19-
# --- FIX: Renamed application ---
2017
super().__init__(None, title="Teatype - Server Manager", size=(600, 400))
21-
2218
SettingsMenuMixin.__init__(self)
23-
2419
self.servers = []
25-
2620
panel = wx.Panel(self)
2721
vbox = wx.BoxSizer(wx.VERTICAL)
28-
2922
self.list_ctrl = wx.ListCtrl(panel, style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
3023
self.list_ctrl.InsertColumn(0, "Name", width=150)
3124
self.list_ctrl.InsertColumn(1, "Hostname", width=150)
3225
self.list_ctrl.InsertColumn(2, "Port", width=60)
3326
self.list_ctrl.InsertColumn(3, "Username", width=120)
3427
vbox.Add(self.list_ctrl, 1, wx.EXPAND | wx.ALL, 5)
35-
3628
hbox = wx.BoxSizer(wx.HORIZONTAL)
3729
self.connect_btn = wx.Button(panel, label="&Connect")
3830
self.add_btn = wx.Button(panel, label="&Add")
3931
self.edit_btn = wx.Button(panel, label="&Edit")
4032
self.remove_btn = wx.Button(panel, label="&Remove")
41-
4233
hbox.Add(self.connect_btn)
4334
hbox.Add(self.add_btn, flag=wx.LEFT, border=5)
4435
hbox.Add(self.edit_btn, flag=wx.LEFT, border=5)
4536
hbox.Add(self.remove_btn, flag=wx.LEFT, border=5)
4637
vbox.Add(hbox, 0, wx.ALIGN_CENTER | wx.BOTTOM | wx.TOP, 5)
47-
4838
panel.SetSizer(vbox)
49-
5039
self.Bind(wx.EVT_BUTTON, self.on_connect, self.connect_btn)
5140
self.Bind(wx.EVT_BUTTON, self.on_add, self.add_btn)
5241
self.Bind(wx.EVT_BUTTON, self.on_edit, self.edit_btn)
5342
self.Bind(wx.EVT_BUTTON, self.on_remove, self.remove_btn)
5443
self.list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_connect)
5544
self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_selection_changed)
5645
self.list_ctrl.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.on_selection_changed)
57-
5846
theme.apply_dark_theme(self)
59-
6047
self.load_servers()
6148
self.populate_list()
6249
self.Centre()
6350
self.Show()
64-
65-
# ... (The rest of the file is unchanged) ...
51+
52+
def update_server_last_path(self, server_name, last_path):
53+
"""Finds a server by name and updates its last_path."""
54+
for server in self.servers:
55+
if server.get("name") == server_name:
56+
server["last_path"] = last_path
57+
self.save_servers()
58+
break
59+
6660
def update_button_states(self):
6761
has_selection = self.list_ctrl.GetFirstSelected() != -1
6862
self.connect_btn.Enable(has_selection)
6963
self.remove_btn.Enable(has_selection)
7064
self.edit_btn.Enable(has_selection)
65+
7166
def on_selection_changed(self, event):
7267
self.update_button_states()
7368
event.Skip()
69+
7470
def load_servers(self):
7571
if os.path.exists(SERVERS_FILE):
7672
try:
7773
with open(SERVERS_FILE, "r") as f: self.servers = json.load(f)
7874
except json.JSONDecodeError: self.servers = []
7975
else: self.servers = []
76+
8077
def save_servers(self):
8178
with open(SERVERS_FILE, "w") as f: json.dump(self.servers, f, indent=4)
79+
8280
def populate_list(self):
8381
self.list_ctrl.DeleteAllItems()
8482
for i, server in enumerate(self.servers):
@@ -87,6 +85,7 @@ def populate_list(self):
8785
self.list_ctrl.SetItem(i, 2, str(server["port"]))
8886
self.list_ctrl.SetItem(i, 3, server["user"])
8987
self.update_button_states()
88+
9089
def on_add(self, event):
9190
with AddServerDialog(self, title="Add SSH Server") as dlg:
9291
if dlg.ShowModal() == wx.ID_OK:
@@ -107,6 +106,7 @@ def on_add(self, event):
107106
self.servers.append(new_server)
108107
self.save_servers()
109108
self.populate_list()
109+
110110
def on_edit(self, event):
111111
selected_index = self.list_ctrl.GetFirstSelected()
112112
if selected_index == -1: return
@@ -124,20 +124,26 @@ def on_edit(self, event):
124124
store_password(server_name=new_data["name"], host=new_data["host"], user=new_data["user"], password=new_data["password"])
125125
elif new_data["auth_method"] == "key" and new_data["passphrase"]:
126126
store_passphrase(server_name=new_data["name"], host=new_data["host"], user=new_data["user"], passphrase=new_data["passphrase"])
127-
updated_server = {
127+
128+
updated_server = original_server.copy()
129+
updated_server.update({
128130
"name": new_data["name"], "host": new_data["host"], "port": new_data["port"],
129131
"user": new_data["user"], "auth_method": new_data["auth_method"],
130132
"password_stored": new_data["store_credential"]
131-
}
133+
})
134+
132135
if new_data["auth_method"] == "key":
133136
updated_server["key_path"] = new_data["key_path"]
134137
if new_data["passphrase"]:
135138
updated_server["has_passphrase"] = True
136139
else:
137-
updated_server["has_passphrase"] = original_server.get("has_passphrase", False)
140+
# If the passphrase field was cleared, we must update has_passphrase
141+
updated_server["has_passphrase"] = False
142+
138143
self.servers[selected_index] = updated_server
139144
self.save_servers()
140145
self.populate_list()
146+
141147
def on_remove(self, event):
142148
selected_index = self.list_ctrl.GetFirstSelected()
143149
if selected_index != -1:
@@ -153,6 +159,7 @@ def on_remove(self, event):
153159
self.servers.pop(selected_index)
154160
self.save_servers()
155161
self.populate_list()
162+
156163
def on_connect(self, event):
157164
selected_index = -1
158165
if isinstance(event, wx.ListEvent):
@@ -181,7 +188,7 @@ def on_connect(self, event):
181188
if server_info.get("password_stored"):
182189
passphrase = get_passphrase(server_name=server_info["name"], host=server_info["host"], user=server_info["user"])
183190
if passphrase is None and server_info.get("has_passphrase", False):
184-
with wx.PasswordEntryDialog(self, f"Enter passphrase for key\n{server_info['key_path']}", "Passphrase Required") as dlg:
191+
with wx.PasswordEntryDialog(self, f"Enter passphrase for key\n{server_info['key_path']}", "Passphrase Required", "Passphrase Required (leave blank if none)") as dlg:
185192
if dlg.ShowModal() == wx.ID_OK: passphrase = dlg.GetValue()
186193
else: return
187194
connect_kwargs['passphrase'] = passphrase

terminal_frame.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# --- START OF FILE terminal_frame.py ---
2+
13
import wx
24
import paramiko
35
import threading
@@ -17,15 +19,15 @@
1719

1820
class TerminalFrame(wx.Frame, SettingsMenuMixin):
1921
def __init__(self, parent, server_info, connect_kwargs):
20-
# ... (init setup is the same) ...
2122
self.server_info = server_info
2223
self.connect_kwargs = connect_kwargs
23-
title = f"Teatype TTY - {server_info['name']} ({server_info['user']}@{server_info['host']})"
24+
title = f"Teatype - {server_info['name']} ({server_info['user']}@{server_info['host']})"
2425
super(TerminalFrame, self).__init__(parent, title=title, size=(800, 600))
2526
SettingsMenuMixin.__init__(self)
2627
self.sftp_client = None
2728
self.temp_dir = tempfile.mkdtemp(prefix="teatype_")
2829
self.open_files = set()
30+
self.sftp_last_path = self.server_info.get("last_path")
2931
panel = wx.Panel(self)
3032
sizer = wx.BoxSizer(wx.VERTICAL)
3133
output_label = wx.StaticText(panel, label="&Server Output:")
@@ -35,7 +37,6 @@ def __init__(self, parent, server_info, connect_kwargs):
3537
self.output_ctrl.SetFont(font)
3638
sizer.Add(self.output_ctrl, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 5)
3739
hbox = wx.BoxSizer(wx.HORIZONTAL)
38-
# --- FIX: Added shortcut to button label ---
3940
self.browse_files_btn = wx.Button(panel, label="&Browse Files (SFTP)")
4041
self.browse_files_btn.Disable()
4142
hbox.Add(self.browse_files_btn)
@@ -63,11 +64,12 @@ def __init__(self, parent, server_info, connect_kwargs):
6364
self.start_ssh_thread()
6465
self.Show()
6566

66-
# ... (all other methods are unchanged and correct) ...
6767
def on_browse_files(self, event):
6868
if self.sftp_client:
69-
with FileBrowserDialog(self, self.sftp_client, edit_callback=self.open_file_for_edit) as dlg:
69+
with FileBrowserDialog(self, self.sftp_client, self.open_file_for_edit, initial_path=self.sftp_last_path) as dlg:
7070
dlg.ShowModal()
71+
self.sftp_last_path = dlg.get_current_path()
72+
7173
def open_file_for_edit(self, remote_path):
7274
if remote_path in self.open_files:
7375
wx.MessageBox(f"This file is already open for editing.", "Already Open", wx.ICON_INFORMATION)
@@ -81,21 +83,27 @@ def open_file_for_edit(self, remote_path):
8183
EditorFrame(self, title, local_path, remote_path, self.sftp_client)
8284
except Exception as e:
8385
wx.MessageBox(f"Failed to open file for editing: {e}", "SFTP Error", wx.ICON_ERROR)
86+
8487
def notify_editor_closed(self, remote_path):
8588
if remote_path in self.open_files:
8689
self.open_files.remove(remote_path)
90+
8791
def on_close(self, event):
8892
try:
89-
if os.path.exists(self.temp_dir):
90-
shutil.rmtree(self.temp_dir)
93+
if os.path.exists(self.temp_dir): shutil.rmtree(self.temp_dir)
9194
except Exception as e:
9295
print(f"Warning: Could not remove temp directory {self.temp_dir}: {e}")
96+
97+
if self.Parent and self.sftp_last_path:
98+
self.Parent.update_server_last_path(self.server_info['name'], self.sftp_last_path)
99+
93100
self.stop_event.set()
94101
if self.ssh_thread and self.ssh_thread.is_alive():
95102
self.ssh_thread.join(timeout=2)
96103
if self.sftp_client: self.sftp_client.close()
97104
if self.ssh_client: self.ssh_client.close()
98105
self.Destroy()
106+
99107
def on_key_down(self, event):
100108
key_code = event.GetKeyCode()
101109
is_ctrl_down = event.ControlDown()
@@ -106,10 +114,12 @@ def on_key_down(self, event):
106114
if self.ssh_channel: self.command_queue.put('\x03')
107115
return
108116
event.Skip()
117+
109118
def start_ssh_thread(self):
110119
self.ssh_thread = threading.Thread(target=self.ssh_worker)
111120
self.ssh_thread.daemon = True
112121
self.ssh_thread.start()
122+
113123
def on_command_enter(self, event):
114124
if not wx.GetKeyState(wx.WXK_SHIFT):
115125
if self.ssh_channel:
@@ -120,23 +130,36 @@ def on_command_enter(self, event):
120130
current_pos = self.input_ctrl.GetInsertionPoint()
121131
self.input_ctrl.WriteText('\n')
122132
self.input_ctrl.SetInsertionPoint(current_pos + 1)
133+
123134
def append_output(self, text):
124135
self.output_ctrl.AppendText(text)
125136
clean_text_for_speech = text.strip()
126137
if clean_text_for_speech:
127138
speak(clean_text_for_speech, interrupt=False)
139+
128140
def filter_control_characters(self, text: str) -> str:
129141
return "".join(c for c in text if c.isprintable() or c in ('\n', '\t'))
142+
130143
def ssh_worker(self):
131144
try:
132145
self.ssh_client = paramiko.SSHClient()
133146
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
134147
wx.CallAfter(self.append_output, f"Connecting to {self.connect_kwargs['hostname']}...\n")
135148
self.ssh_client.connect(**self.connect_kwargs, timeout=10)
149+
150+
# --- FIX: Get the home directory reliably ---
151+
# Use exec_command for a single, non-interactive command
152+
stdin, stdout, stderr = self.ssh_client.exec_command('pwd')
153+
home_dir = stdout.read().decode('utf-8').strip()
154+
if home_dir and not self.sftp_last_path:
155+
self.sftp_last_path = home_dir
156+
136157
self.ssh_channel = self.ssh_client.invoke_shell(term='xterm')
137158
self.sftp_client = self.ssh_client.open_sftp()
159+
138160
wx.CallAfter(self.append_output, "Connection established.\n")
139161
wx.CallAfter(self.browse_files_btn.Enable)
162+
140163
while not self.stop_event.is_set():
141164
if self.ssh_channel.exit_status_ready(): break
142165
if self.ssh_channel.recv_ready():

vdata.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Version: 1.1.0.0
1+
Version: 1.2.0.0
22
CompanyName: Seediffusion
33
FileDescription: Teatype
44
InternalName: CDFN.Teatype

0 commit comments

Comments
 (0)