Skip to content

Commit 777f452

Browse files
authored
Merge pull request #8 from RuVP/ImplementLoggingService
Add logging service
2 parents d24535e + 9bd0010 commit 777f452

File tree

8 files changed

+232
-25
lines changed

8 files changed

+232
-25
lines changed

An2WinFileTransfer/An2WinFileTransfer/An2WinFileTransfer.csproj

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<FileAlignment>512</FileAlignment>
1313
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
1414
<Deterministic>true</Deterministic>
15+
<NuGetPackageImportStamp>
16+
</NuGetPackageImportStamp>
1517
</PropertyGroup>
1618
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1719
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -39,9 +41,40 @@
3941
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
4042
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
4143
</Reference>
44+
<Reference Include="Serilog, Version=4.3.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
45+
<HintPath>..\packages\Serilog.4.3.1-dev-02387\lib\net471\Serilog.dll</HintPath>
46+
</Reference>
47+
<Reference Include="Serilog.Settings.AppSettings, Version=3.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
48+
<HintPath>..\packages\Serilog.Settings.AppSettings.3.0.0\lib\net471\Serilog.Settings.AppSettings.dll</HintPath>
49+
</Reference>
50+
<Reference Include="Serilog.Sinks.File, Version=7.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
51+
<HintPath>..\packages\Serilog.Sinks.File.7.0.0\lib\net471\Serilog.Sinks.File.dll</HintPath>
52+
</Reference>
4253
<Reference Include="System" />
54+
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
55+
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
56+
</Reference>
4357
<Reference Include="System.Configuration" />
4458
<Reference Include="System.Core" />
59+
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
60+
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
61+
</Reference>
62+
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
63+
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
64+
</Reference>
65+
<Reference Include="System.Numerics" />
66+
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
67+
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
68+
</Reference>
69+
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
70+
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
71+
</Reference>
72+
<Reference Include="System.Threading.Channels, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
73+
<HintPath>..\packages\System.Threading.Channels.8.0.0\lib\net462\System.Threading.Channels.dll</HintPath>
74+
</Reference>
75+
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
76+
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
77+
</Reference>
4578
<Reference Include="System.Xml.Linq" />
4679
<Reference Include="System.Data.DataSetExtensions" />
4780
<Reference Include="Microsoft.CSharp" />
@@ -58,10 +91,12 @@
5891
<Compile Include="Extensions\EAppSettingsExtensions.cs" />
5992
<Compile Include="Extensions\EnumerableExtensions.cs" />
6093
<Compile Include="Extensions\StringExtensions.cs" />
94+
<Compile Include="Interfaces\ILoggingService.cs" />
6195
<Compile Include="Models\BackupFileEntry.cs" />
6296
<Compile Include="Models\BackupManifest.cs" />
6397
<Compile Include="Services\BackupService.cs" />
6498
<Compile Include="Services\DeviceService.cs" />
99+
<Compile Include="Services\LoggingService.cs" />
65100
<Compile Include="UI\Forms\FormMain.cs">
66101
<SubType>Form</SubType>
67102
</Compile>
@@ -99,4 +134,11 @@
99134
<None Include="App.config" />
100135
</ItemGroup>
101136
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
137+
<Import Project="..\packages\Serilog.4.3.1-dev-02387\build\Serilog.targets" Condition="Exists('..\packages\Serilog.4.3.1-dev-02387\build\Serilog.targets')" />
138+
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
139+
<PropertyGroup>
140+
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
141+
</PropertyGroup>
142+
<Error Condition="!Exists('..\packages\Serilog.4.3.1-dev-02387\build\Serilog.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Serilog.4.3.1-dev-02387\build\Serilog.targets'))" />
143+
</Target>
102144
</Project>
Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
1-
<?xml version="1.0" encoding="utf-8" ?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
3-
<startup>
4-
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
5-
</startup>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
5+
</startup>
66
<appSettings>
7-
<add key="DefaultBackupFolderPath" value="C:\Temp\Android Backup Test"/>
8-
<add key="FileTypesFilter" value="avi-N;bmp-Y;csv-N;doc-N;docx-N;gif-N;jpg-Y;mp3-Y;mp4-Y;mpeg-N;png-Y;xls-N;xlsx-N;"/>
9-
<add key="CopyAllFiles" value="false"/>
10-
<add key="MtpPath" value="Internal shared storage"/>
7+
<add key="DefaultBackupFolderPath" value="C:\Temp\Android Backup Test" />
8+
<add key="FileTypesFilter" value="avi-N;bmp-Y;csv-N;doc-N;docx-N;gif-N;jpg-Y;mp3-Y;mp4-Y;mpeg-N;png-Y;xls-N;xlsx-N;" />
9+
<add key="CopyAllFiles" value="false" />
10+
<add key="MtpPath" value="Internal shared storage" />
11+
12+
<!-- Minimum log level -->
13+
<add key="serilog:minimum-level" value="Information" />
14+
15+
<!-- File sink configuration -->
16+
<add key="serilog:using:File" value="Serilog.Sinks.File" />
17+
<add key="serilog:write-to:File.path" value="Logs\app.log" />
18+
<add key="serilog:write-to:File.rollingInterval" value="Day" />
19+
<add key="serilog:write-to:File.retainedFileCountLimit" value="7" />
20+
<add key="serilog:write-to:File.outputTemplate"
21+
value="{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}" />
22+
23+
<!-- Optional: enrich logs -->
24+
<add key="serilog:enrich:FromLogContext" value="true" />
1125
</appSettings>
26+
<runtime>
27+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
28+
<dependentAssembly>
29+
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
30+
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
31+
</dependentAssembly>
32+
<dependentAssembly>
33+
<assemblyIdentity name="Serilog" publicKeyToken="24c2f752a8e58a10" culture="neutral" />
34+
<bindingRedirect oldVersion="0.0.0.0-4.3.0.0" newVersion="4.3.0.0" />
35+
</dependentAssembly>
36+
</assemblyBinding>
37+
</runtime>
1238
</configuration>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace An2WinFileTransfer.Interfaces
4+
{
5+
public interface ILoggingService
6+
{
7+
void Info(string message, bool includeUI = true);
8+
void Warn(string message, bool includeUI = true);
9+
void Error(string message, Exception ex = null, bool includeUI = true);
10+
}
11+
}

An2WinFileTransfer/An2WinFileTransfer/Program.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Windows.Forms;
33
using An2WinFileTransfer.UI.Forms;
4+
using Serilog;
45

56
namespace An2WinFileTransfer
67
{
@@ -12,9 +13,27 @@ internal static class Program
1213
[STAThread]
1314
static void Main()
1415
{
16+
// Initialize Serilog from App.config
17+
Log.Logger = new LoggerConfiguration()
18+
.ReadFrom.AppSettings()
19+
.CreateLogger();
20+
1521
Application.EnableVisualStyles();
1622
Application.SetCompatibleTextRenderingDefault(false);
17-
Application.Run(new FormMain());
23+
24+
try
25+
{
26+
Log.Information("Application started.");
27+
Application.Run(new FormMain());
28+
}
29+
catch (Exception ex)
30+
{
31+
Log.Fatal(ex, "Application terminated unexpectedly!");
32+
}
33+
finally
34+
{
35+
Log.CloseAndFlush();
36+
}
1837
}
1938
}
2039
}

An2WinFileTransfer/An2WinFileTransfer/Services/BackupService.cs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Linq;
55
using An2WinFileTransfer.Enums;
6+
using An2WinFileTransfer.Interfaces;
67
using An2WinFileTransfer.Models;
78
using MediaDevices;
89
using Newtonsoft.Json;
@@ -11,29 +12,29 @@ namespace An2WinFileTransfer.Services
1112
{
1213
public class BackupService
1314
{
14-
private readonly Action<string> _logAction;
15+
private readonly ILoggingService _logService;
1516

16-
public BackupService(Action<string> logAction)
17+
public BackupService(ILoggingService log)
1718
{
18-
_logAction = logAction ?? (_ => { });
19+
_logService = log ?? throw new ArgumentNullException(nameof(log));
1920
}
2021

2122
public void BackupFromDevice(MediaDevice device, string sourcePath, string targetRoot, IEnumerable<FileType> fileTypes, bool copyAllFiles)
2223
{
2324
if (!device.DirectoryExists(sourcePath))
2425
{
25-
_logAction($"Source folder not found: {sourcePath}");
26+
_logService.Warn($"Source folder not found: {sourcePath}");
2627
return;
2728
}
2829

2930
var timestampedRootFolder = CreateNewTimeStampedFolder(targetRoot);
3031

31-
_logAction("Scanning previous backups...");
32+
_logService.Info("Scanning previous backups...");
3233

3334
var previousManifests = LoadPreviousManifests(targetRoot);
3435
var existingFiles = BuildExistingFileMap(previousManifests);
3536

36-
_logAction($"Loaded {existingFiles.Count} entries from previous backups.");
37+
_logService.Info($"Loaded {existingFiles.Count} entries from previous backups.");
3738

3839
var manifest = new BackupManifest
3940
{
@@ -42,7 +43,7 @@ public void BackupFromDevice(MediaDevice device, string sourcePath, string targe
4243
Files = new List<BackupFileEntry>()
4344
};
4445

45-
_logAction("Evaluating files to backup...");
46+
_logService.Info("Evaluating files to backup...");
4647

4748
var enabledExtensions = new HashSet<string>(
4849
fileTypes.Where(ft => ft.IsEnabled && !string.IsNullOrWhiteSpace(ft.Extension))
@@ -68,14 +69,16 @@ public void BackupFromDevice(MediaDevice device, string sourcePath, string targe
6869
processedFileCount++;
6970
var fileInfo = device.GetFileInfo(file);
7071

71-
_logAction($"Processing file {processedFileCount} of {totalFileCount}. Copied: {copiedFileCount} | Skipped: {skippedFileCount} | Failed: {copyFailedFileCount}");
72+
_logService.Info($"Processing file {processedFileCount} of {totalFileCount}. Copied: {copiedFileCount} | Skipped: {skippedFileCount} | Failed: {copyFailedFileCount}");
7273

7374
if (fileInfo == null)
7475
{
7576
copyFailedFileCount++;
7677
continue;
7778
}
7879

80+
_logService.Info($"Evaluating file: {file}");
81+
7982
var relativePath = GetRelativePath(sourcePath, file);
8083

8184
var entry = new BackupFileEntry
@@ -95,7 +98,7 @@ public void BackupFromDevice(MediaDevice device, string sourcePath, string targe
9598
continue;
9699
}
97100

98-
var localPath = Path.Combine(timestampedRootFolder, relativePath);
101+
var localPath = Path.Combine(timestampedRootFolder, SanitizePath(relativePath));
99102
Directory.CreateDirectory(Path.GetDirectoryName(localPath));
100103

101104
// Skip if file already backed up in a previous manifest
@@ -133,7 +136,7 @@ public void BackupFromDevice(MediaDevice device, string sourcePath, string targe
133136
{
134137
entry.CopyStatus = ECopyStatus.Failed;
135138
copyFailedFileCount++;
136-
_logAction($"Error copying {file}: {ex.Message}");
139+
_logService.Error($"Error copying {file}: {ex.Message}");
137140
}
138141
}
139142

@@ -145,14 +148,14 @@ public void BackupFromDevice(MediaDevice device, string sourcePath, string targe
145148
var json = JsonConvert.SerializeObject(manifest, Formatting.Indented);
146149
File.WriteAllText(manifestPath, json);
147150

148-
_logAction($"Backup manifest saved: {manifestPath}. Backup duration: {manifest.BackupDuration}.");
151+
_logService.Info($"Backup manifest saved: {manifestPath}. Backup duration: {manifest.BackupDuration}.");
149152
}
150153
catch (Exception ex)
151154
{
152-
_logAction($"Failed to save manifest: {ex.Message}");
155+
_logService.Error($"Failed to save manifest: {ex.Message}");
153156
}
154157

155-
_logAction($"Backup completed: Copied={copiedFileCount}, Skipped={skippedFileCount}, Failed={copyFailedFileCount}, Total={processedFileCount}");
158+
_logService.Info($"Backup completed: Copied={copiedFileCount}, Skipped={skippedFileCount}, Failed={copyFailedFileCount}, Total={processedFileCount}");
156159
}
157160

158161
private string GetRelativePath(string basePath, string fullPath)
@@ -215,13 +218,13 @@ private List<BackupManifest> LoadPreviousManifests(string baseBackupPath)
215218
}
216219
catch (Exception ex)
217220
{
218-
_logAction($"Failed to read manifest {file}: {ex.Message}");
221+
_logService.Info($"Failed to read manifest {file}: {ex.Message}");
219222
}
220223
}
221224
}
222225
catch (Exception ex)
223226
{
224-
_logAction($"Error while scanning for manifests: {ex.Message}");
227+
_logService.Error($"Error while scanning for manifests: {ex.Message}");
225228
}
226229

227230
return manifests;
@@ -244,5 +247,31 @@ private Dictionary<string, BackupFileEntry> BuildExistingFileMap(IEnumerable<Bac
244247

245248
return map;
246249
}
250+
251+
private string SanitizePath(string relativePath)
252+
{
253+
foreach (var pathPart in relativePath.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries))
254+
{
255+
var safePart = SanitizeFileOrFolderName(pathPart);
256+
relativePath = relativePath.Replace(pathPart, safePart);
257+
}
258+
259+
return relativePath;
260+
}
261+
262+
private string SanitizeFileOrFolderName(string name)
263+
{
264+
var invalidChars = Path.GetInvalidFileNameChars()
265+
.Concat(Path.GetInvalidPathChars())
266+
.Distinct()
267+
.ToArray();
268+
269+
var safePath = string.Join("_",
270+
name.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
271+
.Select(part => string.Concat(part.Select(ch => invalidChars.Contains(ch) ? '_' : ch)))
272+
);
273+
274+
return safePath;
275+
}
247276
}
248277
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using An2WinFileTransfer.Interfaces;
3+
using Serilog;
4+
5+
namespace An2WinFileTransfer.Services
6+
{
7+
public class LoggingService : ILoggingService
8+
{
9+
private readonly ILogger _logger;
10+
private readonly Action<string> _uiLogAction;
11+
12+
public LoggingService(ILogger logger, Action<string> uiLogAction = null)
13+
{
14+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
15+
_uiLogAction = uiLogAction;
16+
}
17+
18+
public void Info(string message, bool includeUI = true)
19+
{
20+
_logger.Information(message);
21+
22+
if (includeUI)
23+
{
24+
_uiLogAction?.Invoke(message);
25+
}
26+
}
27+
28+
public void Warn(string message, bool includeUI = true)
29+
{
30+
_logger.Warning(message);
31+
32+
if (includeUI)
33+
{
34+
_uiLogAction?.Invoke("⚠️ " + message);
35+
}
36+
}
37+
38+
public void Error(string message, Exception ex = null, bool includeUI = true)
39+
{
40+
_logger.Error(ex, message);
41+
42+
if (includeUI)
43+
{
44+
_uiLogAction?.Invoke($"❌ {message}: {ex?.Message}");
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)