diff --git a/NewFTPServer/FTPClient/Client.cs b/NewFTPServer/FTPClient/Client.cs new file mode 100644 index 0000000..434312f --- /dev/null +++ b/NewFTPServer/FTPClient/Client.cs @@ -0,0 +1,89 @@ +using System.Net.Sockets; +using System.Text; + +namespace SimpleFTP; + +/// +/// FTP Client implementation class +/// +public class Client +{ + private static int port; + private static string? hostname; + + /// + /// Class constructor + /// + public Client(int port, string hostname) + { + Client.port = port; + Client.hostname = hostname; + Console.WriteLine("Client started!"); + } + + /// + /// Downloading a file from the server + /// + /// The relative path of the file from the specified path on the server + public async Task Get(string filePath) + { + if (hostname == null) + { + throw new ArgumentNullException(); + } + + var client = new TcpClient(); + await client.ConnectAsync(hostname, port); + + var stream = client.GetStream(); + + await stream.WriteAsync(Encoding.UTF8.GetBytes($"2 {filePath}\n")); + await stream.FlushAsync(); + + return await GetResultFromStreamForGet(stream); + } + + /// + /// Listing files in a directory on the server + /// + /// The relative path of the directory from the specified path on the server + public async Task List(string directoryPath) + { + if (hostname == null) + { + throw new ArgumentNullException(); + } + + var client = new TcpClient(); + await client.ConnectAsync(hostname, port); + + var stream = client.GetStream(); + + await stream.WriteAsync(Encoding.UTF8.GetBytes($"1 {directoryPath}\n")); + await stream.FlushAsync(); + + return await GetResultFromStreamForList(stream); + } + + private static async Task GetResultFromStreamForGet(NetworkStream stream) + { + var buffer = new byte[4096]; + + var sizeResult = await stream.ReadAsync(buffer, 0, buffer.Length); + var result = new StringBuilder(); + result.Append(Encoding.UTF8.GetString(buffer, 0, sizeResult)); + + return result.ToString(); + } + + private static async Task GetResultFromStreamForList(NetworkStream stream) + { + var reader = new StreamReader(stream, Encoding.UTF8); + var result = await reader.ReadLineAsync(); + if (result != null) + { + result.Append('\n'); + } + return result; + } +} \ No newline at end of file diff --git a/NewFTPServer/FTPClient/FTPClient.csproj b/NewFTPServer/FTPClient/FTPClient.csproj new file mode 100644 index 0000000..f02677b --- /dev/null +++ b/NewFTPServer/FTPClient/FTPClient.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/NewFTPServer/FTPClient/Program.cs b/NewFTPServer/FTPClient/Program.cs new file mode 100644 index 0000000..ed17c1a --- /dev/null +++ b/NewFTPServer/FTPClient/Program.cs @@ -0,0 +1,5 @@ +using SimpleFTP; + +var client = new Client(8888, "localhost"); +var result = await client.Get("../local.txt"); +Console.WriteLine(result); \ No newline at end of file diff --git a/NewFTPServer/FTPServer/FTPServer.csproj b/NewFTPServer/FTPServer/FTPServer.csproj new file mode 100644 index 0000000..f02677b --- /dev/null +++ b/NewFTPServer/FTPServer/FTPServer.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/NewFTPServer/FTPServer/Program.cs b/NewFTPServer/FTPServer/Program.cs new file mode 100644 index 0000000..e10f04c --- /dev/null +++ b/NewFTPServer/FTPServer/Program.cs @@ -0,0 +1,16 @@ +using SimpleFTP; + +class Program +{ + static async Task Main() + { + Server server = new SimpleFTP.Server("C:/", 8888); + + Task serverTask = Task.Run(async () => await server.Start()); + + Console.WriteLine("нажмите на любую клавишу для остановки серверка"); + Console.ReadKey(); + + server.Stop(); + } +} \ No newline at end of file diff --git a/NewFTPServer/FTPServer/Server.cs b/NewFTPServer/FTPServer/Server.cs new file mode 100644 index 0000000..c8e70ce --- /dev/null +++ b/NewFTPServer/FTPServer/Server.cs @@ -0,0 +1,197 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace SimpleFTP; + +/// +/// A class that implements an FTP server +/// +public class Server +{ + private static string? locate; + private static TcpListener? listener; + private static List? clients; + private static CancellationTokenSource? cancellationToken; + private static List? tasks; + + /// + /// Class constructor + /// + /// The absolute path of the server location + public Server(string locate, int port) + { + if (locate == null) + { + throw new ArgumentNullException(); + } + Server.locate = locate; + listener = new(IPAddress.Any, port); + cancellationToken = new(); + tasks = new(); + clients = new(); + } + + /// + /// Starting the server + /// + public async Task Start() + { + if (listener == null || clients == null + || tasks == null || cancellationToken == null) + { + throw new ArgumentNullException(); + } + + listener.Start(); + Console.WriteLine("Server started working"); + while (!cancellationToken.IsCancellationRequested) + { + Console.WriteLine("Waiting a client"); + var client = await listener.AcceptTcpClientAsync(cancellationToken.Token); + clients.Add(client); + tasks.Add(Listen(client)); + } + + Task.WaitAll(tasks.ToArray()); + foreach (var client in clients) + { + client.Close(); + } + + tasks.Clear(); + + listener.Stop(); + } + + private static Task Listen(TcpClient client) + { + if (cancellationToken == null) + { + Stop(); + throw new ArgumentNullException(nameof(cancellationToken)); + } + + return Task.Run(async () => + { + if (client == null) + { + Stop(); + throw new ArgumentNullException("client"); + } + + var stream = client.GetStream(); + + if (!cancellationToken.IsCancellationRequested) + { + var reader = new StreamReader(stream, Encoding.UTF8); + var stringCommand = await reader.ReadLineAsync(); + + if (stringCommand == null) + { + stream.Close(); + Stop(); + throw new InvalidOperationException(); + } + + if (stringCommand[0] == '1') + { + List(stringCommand.TrimStart('1', ' '), stream); + } + else + { + Get(stringCommand.TrimStart('2', ' '), stream); + } + } + } + ); + } + + /// + /// Server shutdown + /// + public static void Stop() + { + if (listener != null && cancellationToken != null) + { + cancellationToken.Cancel(); + } + } + + private static void Get(string directory, NetworkStream stream) + { + Task.Run(async () => + { + if (locate == null) + { + stream.Close(); + Stop(); + throw new NullReferenceException(); + } + + string combinePath = Path.Combine(locate, directory); + DirectoryInfo info = new DirectoryInfo(combinePath); + var path = info.FullName; + + if (!File.Exists(path)) + { + await stream.WriteAsync(Encoding.UTF8.GetBytes("-1")); + } + else + { + var textBytes = File.ReadAllBytes(path); + await stream.WriteAsync(Encoding.UTF8.GetBytes($"{textBytes.Length} ")); + await stream.WriteAsync(textBytes); + } + stream.Close(); + }); + } + private static void List(string directory, NetworkStream stream) + { + Task.Run(async () => + { + if (locate == null) + { + stream.Close(); + Stop(); + throw new NullReferenceException(); + } + + string combinePath = Path.Combine(locate, directory); + DirectoryInfo info = new DirectoryInfo(combinePath); + var path = info.FullName; + + if (!Directory.Exists(path)) + { + await stream.WriteAsync(Encoding.UTF8.GetBytes("-1\n")); + } + else + { + var filesAndDirectories = new StringBuilder(); + var directories = Directory.GetDirectories(path); + + var sizeFilesAndDirectories = directories.Length; + + foreach (var directory in directories) + { + filesAndDirectories.Append(" " + directory.Substring(directory.LastIndexOf('\\') + 1) + " true"); + } + + var files = Directory.GetFiles(path); + sizeFilesAndDirectories += files.Length; + + foreach (var file in files) + { + filesAndDirectories.Append(" " + file.Substring(file.LastIndexOf('\\') + 1) + " false"); + } + + filesAndDirectories.Append('\n'); + filesAndDirectories.Insert(0, sizeFilesAndDirectories.ToString()); + + await stream.WriteAsync(Encoding.UTF8.GetBytes(filesAndDirectories.ToString())); + } + stream.Close(); + }); + } +} \ No newline at end of file diff --git a/NewFTPServer/NewFTPServer.sln b/NewFTPServer/NewFTPServer.sln new file mode 100644 index 0000000..d32f6ba --- /dev/null +++ b/NewFTPServer/NewFTPServer.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33403.182 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FTPServer", "FTPServer\FTPServer.csproj", "{D9E2CBBA-EE73-4149-A774-70DC12B4886E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FTPClient", "FTPClient\FTPClient.csproj", "{1C09410D-0CEF-4464-8162-7F4F16EC5066}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestsFTP", "TestsFTP\TestsFTP.csproj", "{98F4C7F9-B324-4D82-AE9B-965025AF9B6C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Release|Any CPU.Build.0 = Release|Any CPU + {1C09410D-0CEF-4464-8162-7F4F16EC5066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C09410D-0CEF-4464-8162-7F4F16EC5066}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C09410D-0CEF-4464-8162-7F4F16EC5066}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C09410D-0CEF-4464-8162-7F4F16EC5066}.Release|Any CPU.Build.0 = Release|Any CPU + {98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {39F5B087-17DF-4DAF-9AC5-B12D1F761E54} + EndGlobalSection +EndGlobal diff --git a/NewFTPServer/TestsFTP/TestsFTP.cs b/NewFTPServer/TestsFTP/TestsFTP.cs new file mode 100644 index 0000000..f61a7ca --- /dev/null +++ b/NewFTPServer/TestsFTP/TestsFTP.cs @@ -0,0 +1,52 @@ +namespace TestsFTP; +using SimpleFTP; + +public class Tests +{ + Server server = new SimpleFTP.Server(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsFTP"), 7777); + Client client = new Client(7777, "localhost"); + + [SetUp] + public void SetUp() + { + if (server == null) + { + return; + } + Task.Run(() => server.Start()); + } + + [Test] + public void GettingInformationFromANonExistentFile() + { + var result = client.Get("./test.txt"); + Assert.That(result.Result, Is.EqualTo("-1")); + } + + [Test] + public void GettingInformationFromExistentFile() + { + var result = client.Get("./testForGet.txt"); + Assert.That(result.Result, Is.EqualTo("3 123")); + } + + [Test] + public void GettingInformationFromNonExistentDirectory() + { + var result = client.List("./testForLister"); + Assert.That(result.Result, Is.EqualTo("-1")); + } + + [Test] + public void GettingInformationFromExistentDirectory() + { + var result = client.List("./testForList"); + Assert.That(result.Result, Is.EqualTo("3 test3 true test1.txt false test2.txt false")); + } + + [TearDown] + public void Teardown() + { + server.Stop(); + } +} \ No newline at end of file diff --git a/NewFTPServer/TestsFTP/TestsFTP.csproj b/NewFTPServer/TestsFTP/TestsFTP.csproj new file mode 100644 index 0000000..a613fd4 --- /dev/null +++ b/NewFTPServer/TestsFTP/TestsFTP.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + diff --git a/NewFTPServer/TestsFTP/Usings.cs b/NewFTPServer/TestsFTP/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/NewFTPServer/TestsFTP/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForGet.txt b/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForGet.txt new file mode 100644 index 0000000..d800886 --- /dev/null +++ b/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForGet.txt @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForList/test1.txt b/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForList/test1.txt new file mode 100644 index 0000000..e69de29 diff --git a/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForList/test2.txt b/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForList/test2.txt new file mode 100644 index 0000000..e69de29 diff --git a/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForList/test3/test4.txt b/NewFTPServer/TestsFTP/bin/Debug/net7.0/TestsFTP/testForList/test3/test4.txt new file mode 100644 index 0000000..e69de29