Skip to content

Commit 91740b3

Browse files
authored
Merge pull request #24 from SuessLabs/feature/AsyncUpload
Enhancement includes: * Fixed: Remote folder pre-cleanup. * Added: Upload files async to reduce locking of Visual Studio * Added: Removal of `launch.json` if it previously existed * Added: More output logging. * Update: v1.8.1 * Update: Enhanced Output * Updated: Output Window drop-down title to "Linux Debugger" from "Remote Debugger"
2 parents edf6a0b + 88f2abc commit 91740b3

File tree

7 files changed

+101
-83
lines changed

7 files changed

+101
-83
lines changed

src/VsLinuxDebugger/Commands.Impl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ private async Task<bool> ExecuteBuildAsync(BuildOptions buildOptions)
3636

3737
if (!dbg.IsProjectValid())
3838
{
39-
Console.WriteLine("No C# startup project/solution loaded.");
39+
Logger.Output("No C# startup project/solution loaded.");
4040
return false;
4141
}
4242

4343
if (!await dbg.BeginAsync(buildOptions))
4444
{
45-
Console.WriteLine("Failed to perform actions.");
45+
Logger.Output("Failed to perform actions.");
4646
return false;
4747
}
4848

src/VsLinuxDebugger/Core/LaunchBuilder.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,10 @@ public LaunchBuilder(DTE2 dte, Project dteProject, UserOptions o)
5555
public string ProjectName { get; set; }
5656

5757
/// <summary>Full path to the remote assembly. (i.e. `/home/USER/VLSDbg/Proj/ConsoleApp1.dll`)</summary>
58-
public string RemoteDeployAppPath => LinuxPath.Combine(RemoteDeployFolder, $"{AssemblyName}.dll");
58+
public string RemoteDeployAssemblyFilePath => LinuxPath.Combine(RemoteDeployProjectFolder, $"{AssemblyName}.dll");
5959

6060
/// <summary>Folder of our remote assembly. (i.e. `/home/USER/VLSDbg/Proj`)</summary>
61-
public string RemoteDeployFolder =>
62-
LinuxPath.Combine(_opts.RemoteDeployBasePath, ProjectName);
61+
public string RemoteDeployProjectFolder => LinuxPath.Combine(_opts.RemoteDeployBasePath, ProjectName);
6362

6463
public string RemoteDotNetPath => _opts.RemoteDotNetPath;
6564

@@ -90,7 +89,7 @@ public string GenerateLaunchJson(bool vsdbgLogging = false)
9089

9190
var vsdbgLogPath = "";
9291
if (vsdbgLogging)
93-
vsdbgLogPath = $" --engineLogging={LinuxPath.Combine(RemoteDeployFolder, "_vsdbg.log")}";
92+
vsdbgLogPath = $" --engineLogging={LinuxPath.Combine(RemoteDeployProjectFolder, "_vsdbg.log")}";
9493

9594
if (!_opts.LocalPlinkEnabled)
9695
{
@@ -119,7 +118,7 @@ public string GenerateLaunchJson(bool vsdbgLogging = false)
119118
var obj = new Launch(
120119
RemoteDotNetPath,
121120
$"{AssemblyName}.dll", /// RemoteDeployAppPath,
122-
RemoteDeployFolder,
121+
RemoteDeployProjectFolder,
123122
default,
124123
false)
125124
{
@@ -142,7 +141,7 @@ public string GenerateLaunchJson(bool vsdbgLogging = false)
142141
}
143142
catch (Exception ex)
144143
{
145-
Console.WriteLine($"Error writing 'launch.json' to path, '{outputPath}'!\n{ex.Message}");
144+
Logger.Output($"Error writing 'launch.json' to path, '{outputPath}'!\n{ex.Message}");
146145
outputPath = string.Empty;
147146
}
148147

src/VsLinuxDebugger/Core/RemoteDebugger.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public async Task<bool> BeginAsync(BuildOptions buildOptions)
6262
{
6363
if (!ssh.Connect())
6464
{
65-
Console.WriteLine("Could not connect to remote device.");
65+
Logger.Output("Could not connect to remote device.");
6666
return false;
6767
}
6868

src/VsLinuxDebugger/Core/SshTool.cs

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
using System.Collections.Generic;
44
using System.IO;
55
using System.Threading.Tasks;
6-
using Microsoft.VisualStudio;
76
using Microsoft.VisualStudio.Shell;
8-
using Microsoft.VisualStudio.Shell.Interop;
97
using Renci.SshNet;
108
using SharpCompress.Common;
119
using SharpCompress.Writers;
@@ -55,12 +53,19 @@ public string Bash(string command)
5553
/// <param name="fullScrub">Clear entire base deployment folder (TRUE) or just our project.</param>
5654
public void CleanDeploymentFolder(bool fullScrub = false)
5755
{
58-
//// Bash($"sudo rm -rf {_opts.RemoteDeployBasePath}/*");
59-
56+
// Whole deployment folder and hidden files
57+
// rm -rf xxx/* == Contents of the folder but not the folder itself
58+
// rm -rf xxx/{*,.*} == All hidden files and folders
59+
var filesAndFolders = "{*,.*}";
60+
6061
if (fullScrub)
61-
Bash($"rm -rf {_opts.RemoteDeployBasePath}/*");
62+
{
63+
Bash($"rm -rf \"{_opts.RemoteDeployBasePath}/{filesAndFolders}\"");
64+
}
6265
else
63-
Bash($"rm -rf \"{_launch.RemoteDeployAppPath}/*\"");
66+
{
67+
Bash($"rm -rf {_launch.RemoteDeployProjectFolder}/{filesAndFolders}");
68+
}
6469
}
6570

6671
public bool Connect()
@@ -76,7 +81,6 @@ public bool Connect()
7681
else
7782
keyFile = new PrivateKeyFile(_opts.UserPrivateKeyPath, _opts.UserPrivateKeyPassword);
7883
}
79-
8084
}
8185
catch (Exception ex)
8286
{
@@ -177,30 +181,28 @@ public async Task<string> UploadFilesAsync()
177181
{
178182
try
179183
{
180-
Bash($@"mkdir -p {_launch.RemoteDeployFolder}");
184+
// Clean output folder just incase
185+
//// Bash($@"rm -rf {_launch.RemoteDeployFolder}");
181186

182187
// TODO: Rev1 - Iterate through each file and upload it via SCP client or SFTP.
183188
// TODO: Rev2 - Compress _localHost.OutputDirFullName, upload ZIP, and unzip it.
184189
// TODO: Rev3 - Allow for both SFTP and SCP as a backup. This separating connection to a new disposable class.
185-
//// LogOutput($"Connected to {_connectionInfo.Username}@{_connectionInfo.Host}:{_connectionInfo.Port} via SSH and {(_sftpClient != null ? "SFTP" : "SCP")}");
190+
//// Logger.Output($"Connected to {_connectionInfo.Username}@{_connectionInfo.Host}:{_connectionInfo.Port} via SSH and {(_sftpClient != null ? "SFTP" : "SCP")}");
186191

187-
Bash($@"mkdir -p {_launch.RemoteDeployFolder}");
192+
Bash($@"mkdir -p {_launch.RemoteDeployProjectFolder}");
188193

189194
var srcDirInfo = new DirectoryInfo(_launch.OutputDirFullPath);
190195
if (!srcDirInfo.Exists)
191196
throw new DirectoryNotFoundException($"Directory '{_launch.OutputDirFullPath}' not found!");
192197

193198
// Compress files to upload as single `tar.gz`.
194-
// TODO: Use base folder path: var pathTarGz = $"{_opts.RemoteDeployBasePath}/{_tarGzFileName}";
195-
196-
//// var destTarGz = $"{RemoteDeployPath}/{_tarGzFileName}";
197-
var destTarGz = LinuxPath.Combine(_launch.RemoteDeployFolder, _tarGzFileName);
199+
var destTarGz = LinuxPath.Combine(_launch.RemoteDeployProjectFolder, _tarGzFileName);
198200
Logger.Output($"Destination Tar.GZ: '{destTarGz}'");
199201

200-
var success = PayloadCompressAndUpload(_sftp, srcDirInfo, destTarGz);
202+
var success = await PayloadCompressAndUploadAsync(_sftp, srcDirInfo, destTarGz);
201203

202204
// Decompress file
203-
PayloadDecompress(destTarGz, false);
205+
await PayloadDecompressAsync(destTarGz, false);
204206

205207
return string.Empty;
206208
}
@@ -295,93 +297,98 @@ private ConcurrentDictionary<string, FileInfo> GetLocalFiles(DirectoryInfo srcDi
295297
return localFileCache;
296298
}
297299

298-
private void LogOutput(string message)
299-
{
300-
Console.WriteLine($">> {message}");
301-
Logger.Output(message);
302-
}
303-
304300
/// <summary>Compress build contents and upload to remote host.</summary>
305301
/// <param name="sftp">SFTP connection.</param>
306302
/// <param name="srcDirInfo">Build (source) contents directory info.</param>
307303
/// <param name="pathBuildTarGz">Upload path and filename of build's tar.gz file.</param>
308304
/// <returns></returns>
309-
private bool PayloadCompressAndUpload(SftpClient sftp, DirectoryInfo srcDirInfo, string pathBuildTarGz)
305+
private async Task<bool> PayloadCompressAndUploadAsync(SftpClient sftp, DirectoryInfo srcDirInfo, string pathBuildTarGz)
310306
{
311307
var success = false;
312308
var localFiles = GetLocalFiles(srcDirInfo);
313309

314310
// TODO: Delta remote files against local files for changes.
315311
using (Stream tarGzStream = new MemoryStream())
316312
{
317-
try
313+
var outputMsg = string.Empty;
314+
315+
await Task.Run(() =>
318316
{
319-
using (var tarGzWriter = WriterFactory.Open(tarGzStream, ArchiveType.Tar, CompressionType.GZip))
317+
try
320318
{
321-
using (MemoryStream fileStream = new MemoryStream())
319+
using (var tarGzWriter = WriterFactory.Open(tarGzStream, ArchiveType.Tar, CompressionType.GZip))
322320
{
323-
using (BinaryWriter fileWriter = new BinaryWriter(fileStream))
321+
using (MemoryStream fileStream = new MemoryStream())
324322
{
325-
fileWriter.Write(localFiles.Count);
326-
327-
var updateFileCount = 0;
328-
long updateFileSize = 0;
329-
var allFileCount = 0;
330-
long allFileSize = 0;
331-
332-
foreach (var file in localFiles)
323+
using (BinaryWriter fileWriter = new BinaryWriter(fileStream))
333324
{
334-
allFileCount++;
335-
allFileSize += file.Value.Length;
336-
337-
// TODO: Add new cache file entry
338-
//// UpdateCacheEntry.WriteToStream(newCacheFileWriter, file.Key, file.Value);
325+
fileWriter.Write(localFiles.Count);
339326

340-
updateFileCount++;
341-
updateFileSize += file.Value.Length;
327+
var updateFileCount = 0;
328+
long updateFileSize = 0;
329+
var allFileCount = 0;
330+
long allFileSize = 0;
342331

343-
try
344-
{
345-
tarGzWriter.Write(file.Key, file.Value);
346-
}
347-
catch (IOException ioEx)
332+
foreach (var file in localFiles)
348333
{
349-
LogOutput($"Exception: {ioEx.Message}");
334+
allFileCount++;
335+
allFileSize += file.Value.Length;
336+
337+
// TODO: Add new cache file entry
338+
//// UpdateCacheEntry.WriteToStream(newCacheFileWriter, file.Key, file.Value);
339+
340+
updateFileCount++;
341+
updateFileSize += file.Value.Length;
342+
343+
try
344+
{
345+
tarGzWriter.Write(file.Key, file.Value);
346+
}
347+
catch (IOException ioEx)
348+
{
349+
outputMsg += $"Exception: {ioEx.Message}{Environment.NewLine}";
350+
}
351+
catch (Exception ex)
352+
{
353+
outputMsg += $"Exception: {ex.Message}{Environment.NewLine}{ex.StackTrace}{Environment.NewLine}";
354+
}
350355
}
351-
catch (Exception ex)
352-
{
353-
LogOutput($"Exception: {ex.Message}\n{ex.StackTrace}");
354-
}
355-
}
356356

357-
LogOutput($"{updateFileCount,7:n0} [{updateFileSize,13:n0} bytes] of {allFileCount,7:n0} [{allFileSize,13:n0} bytes] files need to be updated");
357+
outputMsg += $"Update file count: {updateFileCount}; File Size: [{updateFileSize} bytes] of Total Files: {allFileCount} [{allFileSize} bytes] need to be updated";
358+
}
358359
}
359360
}
361+
362+
success = true;
363+
}
364+
catch (Exception ex)
365+
{
366+
outputMsg += $"Error while compressing file contents. {ex.Message}\n{ex.StackTrace}";
360367
}
368+
});
361369

362-
success = true;
363-
}
364-
catch (Exception ex)
365-
{
366-
LogOutput($"Error while compressing file contents. {ex.Message}\n{ex.StackTrace}");
367-
}
370+
Logger.Output(outputMsg);
368371

369372
// Upload the file
370373
if (success)
371374
{
372375
try
373376
{
374377
var tarGzSize = tarGzStream.Length;
375-
tarGzStream.Seek(0, SeekOrigin.Begin);
376378

377-
sftp.UploadFile(tarGzStream, pathBuildTarGz);
379+
await Task.Run(() =>
380+
{
381+
tarGzStream.Seek(0, SeekOrigin.Begin);
378382

379-
LogOutput($"Uploaded '{_tarGzFileName}' [{tarGzSize,13:n0} bytes].");
383+
sftp.UploadFile(tarGzStream, pathBuildTarGz);
384+
});
385+
386+
Logger.Output($"Uploaded '{_tarGzFileName}' [{tarGzSize,13:n0} bytes].");
380387
success = true;
381388
}
382389
catch (Exception ex)
383390
{
384-
LogOutput($"Error while uploading file. {ex.Message}\n{ex.StackTrace}");
391+
Logger.Output($"Error while uploading file. {ex.Message}\n{ex.StackTrace}");
385392
success = false;
386393
}
387394
}
@@ -394,20 +401,26 @@ private bool PayloadCompressAndUpload(SftpClient sftp, DirectoryInfo srcDirInfo,
394401
/// <param name="pathBuildTarGz">Path to upload to.</param>
395402
/// <param name="removeTarGz">Remove our build's tar.gz file. Set to FALSE for debugging. (default=true)</param>
396403
/// <returns>Returns true on success.</returns>
397-
private bool PayloadDecompress(string pathBuildTarGz, bool removeTarGz = true)
404+
private async Task<bool> PayloadDecompressAsync(string pathBuildTarGz, bool removeTarGz = true)
398405
{
399406
try
400407
{
408+
var decompressOutput = string.Empty;
409+
401410
var cmd = "set -e";
402-
cmd += $";cd \"{_launch.RemoteDeployFolder}\"";
411+
cmd += $";cd \"{_launch.RemoteDeployProjectFolder}\"";
403412
cmd += $";tar -zxf \"{_tarGzFileName}\"";
404413
////cmd += $";tar -zxf \"{pathBuildTarGz}\"";
405414

406415
if (removeTarGz)
407416
cmd += $";rm \"{pathBuildTarGz}\"";
408417

409-
var output = Bash(cmd);
410-
LogOutput(output);
418+
await Task.Run(() =>
419+
{
420+
decompressOutput = Bash(cmd);
421+
});
422+
423+
Logger.Output($"Payload Decompress results: '{decompressOutput}' (blank=OK)");
411424

412425
return true;
413426
}

src/VsLinuxDebugger/Logger.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ private static string FormattedTime
3232
}
3333
}
3434

35-
public static void Init(IServiceProvider provider, OutputWindowType outputType = OutputWindowType.Debug, string name = "Remote Debugger")
35+
public static void Init(
36+
IServiceProvider provider,
37+
OutputWindowType outputType = OutputWindowType.Debug,
38+
string name = "Linux Debugger")
3639
{
3740
_provider = provider;
3841
_outputType = outputType;
@@ -41,18 +44,21 @@ public static void Init(IServiceProvider provider, OutputWindowType outputType =
4144

4245
public static void Output(string message)
4346
{
47+
var msg = $"{FormattedTime}: {message}{Environment.NewLine}";
48+
4449
try
4550
{
4651
ThreadHelper.ThrowIfNotOnUIThread();
4752

4853
if (HasOutputWindow())
4954
{
50-
_outputPane.OutputStringThreadSafe($"{FormattedTime}: {message}{Environment.NewLine}");
51-
_outputPane.Activate(); // Brings this pane into view
55+
_outputPane.OutputStringThreadSafe(msg);
56+
_outputPane.Activate(); // Brings pane into view
5257
}
5358
}
5459
catch (Exception)
5560
{
61+
Console.Write($"Failed to Output: '{msg}'");
5662
}
5763
}
5864

src/VsLinuxDebugger/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@
2929
// You can specify all the values or you can default the Build and Revision Numbers
3030
// by using the '*' as shown below:
3131
// [assembly: AssemblyVersion("1.0.*")]
32-
[assembly: AssemblyVersion("1.8.0.0")]
33-
[assembly: AssemblyFileVersion("1.8.0.0")]
32+
[assembly: AssemblyVersion("1.8.1.0")]
33+
[assembly: AssemblyFileVersion("1.8.1.0")]

src/VsLinuxDebugger/source.extension.vsixmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
33
<Metadata>
4-
<Identity Id="VsLinuxDebugger.4d7bf4de-5015-4e24-92c0-7f9f3397b2da" Version="1.8.0" Language="en-US" Publisher="Suess Labs" />
4+
<Identity Id="VsLinuxDebugger.4d7bf4de-5015-4e24-92c0-7f9f3397b2da" Version="1.8.1" Language="en-US" Publisher="Suess Labs" />
55
<DisplayName>VS Linux Debugger</DisplayName>
66
<Description xml:space="preserve">Remotely deploy and debug your .NET apps visa SSH on your Linux device using Visual Studio 2022. Works with popular Linux distrobutions such as Ubuntu, Raspberry Pi, and more!</Description>
77
<MoreInfo>https://github.com/SuessLabs/VsLinuxDebug</MoreInfo>

0 commit comments

Comments
 (0)