Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions An2WinFileTransfer/An2WinFileTransfer/An2WinFileTransfer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
Expand Down Expand Up @@ -39,9 +41,40 @@
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Serilog, Version=4.3.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\packages\Serilog.4.3.1-dev-02387\lib\net471\Serilog.dll</HintPath>
</Reference>
<Reference Include="Serilog.Settings.AppSettings, Version=3.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\packages\Serilog.Settings.AppSettings.3.0.0\lib\net471\Serilog.Settings.AppSettings.dll</HintPath>
</Reference>
<Reference Include="Serilog.Sinks.File, Version=7.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\packages\Serilog.Sinks.File.7.0.0\lib\net471\Serilog.Sinks.File.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Channels, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Channels.8.0.0\lib\net462\System.Threading.Channels.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
Expand All @@ -58,10 +91,12 @@
<Compile Include="Extensions\EAppSettingsExtensions.cs" />
<Compile Include="Extensions\EnumerableExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Interfaces\ILoggingService.cs" />
<Compile Include="Models\BackupFileEntry.cs" />
<Compile Include="Models\BackupManifest.cs" />
<Compile Include="Services\BackupService.cs" />
<Compile Include="Services\DeviceService.cs" />
<Compile Include="Services\LoggingService.cs" />
<Compile Include="UI\Forms\FormMain.cs">
<SubType>Form</SubType>
</Compile>
Expand Down Expand Up @@ -99,4 +134,11 @@
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Serilog.4.3.1-dev-02387\build\Serilog.targets" Condition="Exists('..\packages\Serilog.4.3.1-dev-02387\build\Serilog.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<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>
</PropertyGroup>
<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'))" />
</Target>
</Project>
42 changes: 34 additions & 8 deletions An2WinFileTransfer/An2WinFileTransfer/App.config
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<appSettings>
<add key="DefaultBackupFolderPath" value="C:\Temp\Android Backup Test"/>
<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;"/>
<add key="CopyAllFiles" value="false"/>
<add key="MtpPath" value="Internal shared storage"/>
<add key="DefaultBackupFolderPath" value="C:\Temp\Android Backup Test" />
<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;" />
<add key="CopyAllFiles" value="false" />
<add key="MtpPath" value="Internal shared storage" />

<!-- Minimum log level -->
<add key="serilog:minimum-level" value="Information" />

<!-- File sink configuration -->
<add key="serilog:using:File" value="Serilog.Sinks.File" />
<add key="serilog:write-to:File.path" value="Logs\app.log" />
<add key="serilog:write-to:File.rollingInterval" value="Day" />
<add key="serilog:write-to:File.retainedFileCountLimit" value="7" />
<add key="serilog:write-to:File.outputTemplate"
value="{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}" />

<!-- Optional: enrich logs -->
<add key="serilog:enrich:FromLogContext" value="true" />
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Serilog" publicKeyToken="24c2f752a8e58a10" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.3.0.0" newVersion="4.3.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace An2WinFileTransfer.Interfaces
{
public interface ILoggingService
{
void Info(string message, bool includeUI = true);
void Warn(string message, bool includeUI = true);
void Error(string message, Exception ex = null, bool includeUI = true);
}
}
21 changes: 20 additions & 1 deletion An2WinFileTransfer/An2WinFileTransfer/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Windows.Forms;
using An2WinFileTransfer.UI.Forms;
using Serilog;

namespace An2WinFileTransfer
{
Expand All @@ -12,9 +13,27 @@ internal static class Program
[STAThread]
static void Main()
{
// Initialize Serilog from App.config
Log.Logger = new LoggerConfiguration()
.ReadFrom.AppSettings()
.CreateLogger();

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FormMain());

try
{
Log.Information("Application started.");
Application.Run(new FormMain());
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly!");
}
finally
{
Log.CloseAndFlush();
}
}
}
}
59 changes: 44 additions & 15 deletions An2WinFileTransfer/An2WinFileTransfer/Services/BackupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using An2WinFileTransfer.Enums;
using An2WinFileTransfer.Interfaces;
using An2WinFileTransfer.Models;
using MediaDevices;
using Newtonsoft.Json;
Expand All @@ -11,29 +12,29 @@ namespace An2WinFileTransfer.Services
{
public class BackupService
{
private readonly Action<string> _logAction;
private readonly ILoggingService _logService;

public BackupService(Action<string> logAction)
public BackupService(ILoggingService log)
{
_logAction = logAction ?? (_ => { });
_logService = log ?? throw new ArgumentNullException(nameof(log));
}

public void BackupFromDevice(MediaDevice device, string sourcePath, string targetRoot, IEnumerable<FileType> fileTypes, bool copyAllFiles)
{
if (!device.DirectoryExists(sourcePath))
{
_logAction($"Source folder not found: {sourcePath}");
_logService.Warn($"Source folder not found: {sourcePath}");
return;
}

var timestampedRootFolder = CreateNewTimeStampedFolder(targetRoot);

_logAction("Scanning previous backups...");
_logService.Info("Scanning previous backups...");

var previousManifests = LoadPreviousManifests(targetRoot);
var existingFiles = BuildExistingFileMap(previousManifests);

_logAction($"Loaded {existingFiles.Count} entries from previous backups.");
_logService.Info($"Loaded {existingFiles.Count} entries from previous backups.");

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

_logAction("Evaluating files to backup...");
_logService.Info("Evaluating files to backup...");

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

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

if (fileInfo == null)
{
copyFailedFileCount++;
continue;
}

_logService.Info($"Evaluating file: {file}");

var relativePath = GetRelativePath(sourcePath, file);

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

var localPath = Path.Combine(timestampedRootFolder, relativePath);
var localPath = Path.Combine(timestampedRootFolder, SanitizePath(relativePath));
Directory.CreateDirectory(Path.GetDirectoryName(localPath));

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

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

_logAction($"Backup manifest saved: {manifestPath}. Backup duration: {manifest.BackupDuration}.");
_logService.Info($"Backup manifest saved: {manifestPath}. Backup duration: {manifest.BackupDuration}.");
}
catch (Exception ex)
{
_logAction($"Failed to save manifest: {ex.Message}");
_logService.Error($"Failed to save manifest: {ex.Message}");
}

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

private string GetRelativePath(string basePath, string fullPath)
Expand Down Expand Up @@ -215,13 +218,13 @@ private List<BackupManifest> LoadPreviousManifests(string baseBackupPath)
}
catch (Exception ex)
{
_logAction($"Failed to read manifest {file}: {ex.Message}");
_logService.Info($"Failed to read manifest {file}: {ex.Message}");
}
}
}
catch (Exception ex)
{
_logAction($"Error while scanning for manifests: {ex.Message}");
_logService.Error($"Error while scanning for manifests: {ex.Message}");
}

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

return map;
}

private string SanitizePath(string relativePath)
{
foreach (var pathPart in relativePath.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries))
{
var safePart = SanitizeFileOrFolderName(pathPart);
relativePath = relativePath.Replace(pathPart, safePart);
}

return relativePath;
}

private string SanitizeFileOrFolderName(string name)
{
var invalidChars = Path.GetInvalidFileNameChars()
.Concat(Path.GetInvalidPathChars())
.Distinct()
.ToArray();

var safePath = string.Join("_",
name.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.Select(part => string.Concat(part.Select(ch => invalidChars.Contains(ch) ? '_' : ch)))
);

return safePath;
}
}
}
48 changes: 48 additions & 0 deletions An2WinFileTransfer/An2WinFileTransfer/Services/LoggingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using An2WinFileTransfer.Interfaces;
using Serilog;

namespace An2WinFileTransfer.Services
{
public class LoggingService : ILoggingService
{
private readonly ILogger _logger;
private readonly Action<string> _uiLogAction;

public LoggingService(ILogger logger, Action<string> uiLogAction = null)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_uiLogAction = uiLogAction;
}

public void Info(string message, bool includeUI = true)
{
_logger.Information(message);

if (includeUI)
{
_uiLogAction?.Invoke(message);
}
}

public void Warn(string message, bool includeUI = true)
{
_logger.Warning(message);

if (includeUI)
{
_uiLogAction?.Invoke("⚠️ " + message);
}
}

public void Error(string message, Exception ex = null, bool includeUI = true)
{
_logger.Error(ex, message);

if (includeUI)
{
_uiLogAction?.Invoke($"❌ {message}: {ex?.Message}");
}
}
}
}
Loading