Skip to content

Commit e5d9f3e

Browse files
author
PortaSFTPServer
committed
Fix PhotinoSftpServer: [STAThread], InvokeAsync, safe SessionId, SFTP subsystem
1 parent a113d10 commit e5d9f3e

14 files changed

Lines changed: 232 additions & 111 deletions

File tree

ApacheMinaSSHD.NET.Bindings/ApacheMinaSSHD.NET.Bindings.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<!--<TargetFramework>net481</TargetFramework>-->
5-
<TargetFramework>net10.0</TargetFramework>
5+
<TargetFrameworks>net9.0;net10.0</TargetFrameworks>
66

77
<ImplicitUsings>enable</ImplicitUsings>
88
<Nullable>enable</Nullable>

ApacheMinaSSHD.NET.Wrapper/ApacheMinaSSHD.NET.Wrapper.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<!--<TargetFramework>net481</TargetFramework>-->
5-
<TargetFramework>net10.0</TargetFramework>
5+
<TargetFrameworks>net9.0;net10.0</TargetFrameworks>
66

77
<ImplicitUsings>enable</ImplicitUsings>
88
<Nullable>enable</Nullable>

ApacheMinaSSHD.NET.Wrapper/Internals/Models/SshSession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public SshSession(org.apache.sshd.server.session.ServerSession javaSession)
1010
}
1111

1212
public string RemoteAddress => _javaSession.getIoSession().getRemoteAddress().toString();
13-
public Guid SessionId => Guid.Parse(_javaSession.getSessionId().ToString()!);
13+
public Guid SessionId { get; } = Guid.NewGuid();
1414
}
1515

1616
}

Sample/AvaloniaSftpServer/MainWindow.axaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
<TextBlock Text="Port:" VerticalAlignment="Center" Margin="12,0,0,0" />
2525
<TextBox x:Name="PortBox" Text="2222" Width="80" />
2626
<Button x:Name="StartBtn" Content="Start" Margin="12,0,0,0"
27-
Click="OnStartClick" IsEnabled="True" />
27+
Click="OnStartClick" IsEnabled="True"
28+
Background="#2e7d32" Foreground="White" />
2829
<Button x:Name="StopBtn" Content="Stop" Margin="8,0,0,0"
29-
Click="OnStopClick" IsEnabled="False" />
30+
Click="OnStopClick" IsEnabled="False"
31+
Background="#c62828" Foreground="White" />
3032
</StackPanel>
3133
</Border>
3234

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<base href="/" />
7+
<title>ApacheMinaSSHD.NET — SFTP Server Manager</title>
8+
<HeadOutlet />
9+
</head>
10+
<body>
11+
<Routes />
12+
<script src="_framework/blazor.web.js"></script>
13+
</body>
14+
</html>
Lines changed: 46 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
@page "/"
2+
@rendermode InteractiveServer
3+
@inject ServerState State
4+
@implements IDisposable
5+
16
<div class="container">
27
<div class="header">
38
<h1>ApacheMinaSSHD.NET</h1>
@@ -13,10 +18,10 @@
1318
<label>Port:</label>
1419
<input @bind="Port" type="number" />
1520
</div>
16-
<button class="btn start" @onclick="StartServer" disabled="@(_server != null)">
21+
<button class="btn start" @onclick="StartServer" disabled="@(State.Server is not null)">
1722
Start
1823
</button>
19-
<button class="btn stop" @onclick="StopServer" disabled="@(_server is null)">
24+
<button class="btn stop" @onclick="StopServer" disabled="@(State.Server is null)">
2025
Stop
2126
</button>
2227
<span class="status @StatusClass">@StatusText</span>
@@ -33,7 +38,7 @@
3338
</div>
3439
<div class="panel">
3540
<h3>Active Sessions</h3>
36-
@if (_sessions.Count == 0)
41+
@if (State.Sessions.Count == 0)
3742
{
3843
<p class="muted">No active sessions</p>
3944
}
@@ -42,7 +47,7 @@
4247
<table>
4348
<thead><tr><th>ID</th><th>Remote</th><th>Since</th></tr></thead>
4449
<tbody>
45-
@foreach (var s in _sessions)
50+
@foreach (var s in State.Sessions)
4651
{
4752
<tr><td>@s.Id.ToString("N")[..8]</td><td>@s.RemoteAddress</td><td>@s.StartedAt</td></tr>
4853
}
@@ -53,7 +58,7 @@
5358
<div class="panel log-panel">
5459
<h3>Log</h3>
5560
<div class="log-box">
56-
@foreach (var entry in _log)
61+
@foreach (var entry in State.Log)
5762
{
5863
<div class="log-entry">[@entry.Timestamp] @entry.Text</div>
5964
}
@@ -63,7 +68,7 @@
6368

6469
<div class="footer">
6570
<span>ApacheMinaSSHD.NET by SERALYNX LLC</span>
66-
<button class="btn" @onclick="ClearLog">Clear Log</button>
71+
<button class="btn clear-log" @onclick="ClearLog">Clear Log</button>
6772
</div>
6873
</div>
6974

@@ -86,6 +91,8 @@
8691
.btn.start:hover:not(:disabled) { background: #388e3c; }
8792
.btn.stop { background: #c62828; color: #fff; }
8893
.btn.stop:hover:not(:disabled) { background: #d32f2f; }
94+
.clear-log { font-size: .8rem; background: #333; color: #ccc; }
95+
.clear-log:hover { background: #444; }
8996
.status { font-weight: 600; font-size: .85rem; margin-left: auto; }
9097
.status.running { color: #66bb6a; }
9198
.status.stopped { color: #aaa; }
@@ -103,15 +110,11 @@
103110
.log-entry { padding: 1px 4px; }
104111
.log-entry:nth-child(odd) { background: #1e1e32; }
105112
.footer { display: flex; justify-content: space-between; align-items: center; color: #888; font-size: .8rem; }
106-
.footer .btn { font-size: .8rem; background: #333; color: #ccc; }
107113
</style>
108114

109115
@code {
110-
private ApacheMinaSSHD.NET.Wrapper.AMNetSshServer? _server;
111-
private readonly List<LogEntry> _log = [];
112-
private readonly List<SessionInfo> _sessions = [];
113116
private string _statusText = "Stopped";
114-
private string _statusClass = "stopped";
117+
private bool _statusRunning;
115118

116119
private string Host { get; set; } = "127.0.0.1";
117120
private string Port { get; set; } = "2222";
@@ -122,102 +125,67 @@
122125
private bool UseModernAlgos { get; set; } = true;
123126

124127
private string StatusText => _statusText;
125-
private string StatusClass => _statusClass;
128+
private string StatusClass => _statusRunning ? "running" : "stopped";
129+
130+
protected override void OnInitialized()
131+
{
132+
State.Changed += OnStateChanged;
133+
}
134+
135+
private void OnStateChanged() => InvokeAsync(StateHasChanged);
126136

127137
private void StartServer()
128138
{
129139
try
130140
{
131-
_server = ApacheMinaSSHD.NET.Wrapper.AMNetSshServer.SetUpDefaultServer();
132-
_server.Host = Host;
133-
_server.Port = int.Parse(Port);
141+
State.Server = ApacheMinaSSHD.NET.Wrapper.AMNetSshServer.SetUpDefaultServer();
142+
State.Server.Host = Host;
143+
State.Server.Port = int.Parse(Port);
134144

135-
if (UseProductionDefaults) _server.Config.ApplyProductionDefaults();
136-
if (UseModernAlgos) _server.Config.ApplyModernAlgorithmDefaults();
137-
_server.Config.WELCOME_BANNER = "ApacheMinaSSHD.NET — Photino Manager";
145+
if (UseProductionDefaults) State.Server.Config.ApplyProductionDefaults();
146+
if (UseModernAlgos) State.Server.Config.ApplyModernAlgorithmDefaults();
147+
State.Server.Config.WELCOME_BANNER = "ApacheMinaSSHD.NET — Photino Manager";
138148

139-
_server.SetFixedPasswordAuthenticator("admin", Password);
140-
_server.setKeyPairProvider(
149+
State.Server.SetFixedPasswordAuthenticator("admin", Password);
150+
State.Server.setKeyPairProvider(
141151
new ApacheMinaSSHD.NET.Wrapper.Factories.AMNetSimpleGeneratorHostKeyProvider(HostKey));
142152

143-
EnsureDir(StorageRoot);
144-
_server.setFileSystemFactory(
153+
if (!Directory.Exists(StorageRoot)) Directory.CreateDirectory(StorageRoot);
154+
State.Server.setFileSystemFactory(
145155
new ApacheMinaSSHD.NET.Wrapper.Factories.AMNetVirtualFileSystemFactory(StorageRoot));
146156

147-
_server.addSessionListener(new PhotinoSessionListener(this));
148-
_server.Start();
157+
State.Server.setSubsystemFactories(new ApacheMinaSSHD.NET.Wrapper.Factories.AMNetSftpSubsystemFactory());
158+
State.Server.addSessionListener(new PhotinoSessionListener(State));
159+
State.Server.Start();
149160

150161
_statusText = "Running";
151-
_statusClass = "running";
152-
Log("Server started");
153-
StateHasChanged();
162+
_statusRunning = true;
163+
State.LogMessage("Server started");
154164
}
155165
catch (Exception ex)
156166
{
157-
Log($"Start failed: {ex.Message}");
158-
StateHasChanged();
167+
State.LogMessage($"Start failed: {ex.Message}");
159168
}
160169
}
161170

162171
private void StopServer()
163172
{
164173
try
165174
{
166-
_server?.Stop();
167-
_server = null;
168-
_sessions.Clear();
175+
State.Server?.Stop();
176+
State.Server = null;
177+
State.Sessions.Clear();
169178
_statusText = "Stopped";
170-
_statusClass = "stopped";
171-
Log("Server stopped");
172-
StateHasChanged();
179+
_statusRunning = false;
180+
State.LogMessage("Server stopped");
173181
}
174182
catch (Exception ex)
175183
{
176-
Log($"Stop error: {ex.Message}");
177-
StateHasChanged();
184+
State.LogMessage($"Stop error: {ex.Message}");
178185
}
179186
}
180187

181-
private void ClearLog()
182-
{
183-
_log.Clear();
184-
StateHasChanged();
185-
}
186-
187-
internal void Log(string message)
188-
{
189-
_log.Add(new LogEntry(message));
190-
if (_log.Count > 500) _log.RemoveAt(0);
191-
InvokeAsync(StateHasChanged);
192-
}
193-
194-
internal void AddSession(ApacheMinaSSHD.NET.Wrapper.Abstractions.Models.ISshSession session)
195-
{
196-
_sessions.Add(new SessionInfo(session));
197-
InvokeAsync(StateHasChanged);
198-
}
199-
200-
internal void RemoveSession(ApacheMinaSSHD.NET.Wrapper.Abstractions.Models.ISshSession session)
201-
{
202-
_sessions.RemoveAll(s => s.Id == session.SessionId);
203-
InvokeAsync(StateHasChanged);
204-
}
205-
206-
private static void EnsureDir(string path)
207-
{
208-
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
209-
}
210-
211-
public class LogEntry(string text)
212-
{
213-
public string Timestamp { get; } = DateTime.Now.ToString("HH:mm:ss");
214-
public string Text { get; } = text;
215-
}
188+
private void ClearLog() => State.ClearLog();
216189

217-
public class SessionInfo(ApacheMinaSSHD.NET.Wrapper.Abstractions.Models.ISshSession session)
218-
{
219-
public Guid Id => session.SessionId;
220-
public string RemoteAddress => session.RemoteAddress ?? "unknown";
221-
public string StartedAt => DateTime.Now.ToString("HH:mm:ss");
222-
}
190+
public void Dispose() => State.Changed -= OnStateChanged;
223191
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<Router AppAssembly="typeof(Program).Assembly">
2+
<Found Context="routeData">
3+
<RouteView RouteData="routeData" />
4+
<FocusOnNavigate RouteData="routeData" Selector="h1" />
5+
</Found>
6+
</Router>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@using System.Net.Http
2+
@using Microsoft.AspNetCore.Components.Forms
3+
@using Microsoft.AspNetCore.Components.Routing
4+
@using Microsoft.AspNetCore.Components.Web
5+
@using static Microsoft.AspNetCore.Components.Web.RenderMode
6+
@using Microsoft.AspNetCore.Components.Web.Virtualization
7+
@using Microsoft.JSInterop
8+
@using PhotinoSftpServer
9+
@using PhotinoSftpServer.Components
10+
@using ApacheMinaSSHD.NET.Wrapper
11+
@using ApacheMinaSSHD.NET.Wrapper.Factories
12+
@using ApacheMinaSSHD.NET.Wrapper.Abstractions
13+
@using ApacheMinaSSHD.NET.Wrapper.Abstractions.Models
Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Razor">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
23
<PropertyGroup>
3-
<OutputType>WinExe</OutputType>
4-
<TargetFramework>net10.0</TargetFramework>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
56
<Nullable>enable</Nullable>
67
<ImplicitUsings>enable</ImplicitUsings>
7-
<LangVersion>14.0</LangVersion>
8-
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
9-
<Description>Cross-platform Photino Blazor desktop SFTP server manager for .NET — start/stop, session monitoring, and live log viewer. Blazor hybrid UI provides native desktop + web tech. By SERALYNX LLC (Porta SFTP Server).</Description>
8+
<GenerateEntryPoint>false</GenerateEntryPoint>
9+
<Description>Cross-platform Photino desktop SFTP server manager for .NET — start/stop, session monitoring, and live log viewer. Embedded Kestrel serves Blazor Server UI to the Photino WebView. By SERALYNX LLC (Porta SFTP Server).</Description>
1010
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Compile Remove="wwwroot\**" />
14+
<None Update="wwwroot\**">
15+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
16+
</None>
17+
</ItemGroup>
18+
1119
<ItemGroup>
12-
<PackageReference Include="Photino.Blazor" Version="4.*" />
20+
<PackageReference Include="Photino.NET" Version="4.0.16" />
1321
</ItemGroup>
22+
1423
<ItemGroup>
1524
<ProjectReference Include="..\..\ApacheMinaSSHD.NET.Wrapper\ApacheMinaSSHD.NET.Wrapper.csproj" />
1625
</ItemGroup>
17-
</Project>
1826

27+
</Project>

0 commit comments

Comments
 (0)