diff --git a/MyFTP/MyFTP.Test/MyFTP.Test.csproj b/MyFTP/MyFTP.Test/MyFTP.Test.csproj new file mode 100644 index 0000000..dcfd5bc --- /dev/null +++ b/MyFTP/MyFTP.Test/MyFTP.Test.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + + false + + + + + + + + + + + + + + + diff --git a/MyFTP/MyFTP.Test/MyFTPTest.cs b/MyFTP/MyFTP.Test/MyFTPTest.cs new file mode 100644 index 0000000..e43be81 --- /dev/null +++ b/MyFTP/MyFTP.Test/MyFTPTest.cs @@ -0,0 +1,91 @@ +namespace MyFTP.Test; + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MyFTPClient; +using MyFTPServer; +using NUnit.Framework; + +public class Tests +{ + private Server _server; + private Client _client; + private Stream _fileStream; + private CancellationToken _cancellationToken; + + [SetUp] + public void Setup() + { + _server = new Server("127.0.0.1", 80); + _client = new Client("127.0.0.1", 80); + _fileStream = new MemoryStream(); + _cancellationToken = new (); + _server.StartServer(); + Thread.Sleep(5000); + } + + [TearDown] + public void TearDown() + { + _server.StopServer(); + } + + [Test] + public void GetInvalidFileNameTest() + { + Assert.ThrowsAsync(() => _client.Get("Text.txt", _fileStream, _cancellationToken)); + } + + [Test] + public void ListInvalidFileNameTest() + { + Assert.ThrowsAsync(() => _client.Get("Text.txt", _fileStream, _cancellationToken)); + } + + [Test] + public async Task ListTest() + { + var data = await _client.List("../../../TestData", _cancellationToken); + Assert.AreEqual("../../../TestData\\Data.txt", data[0].name); + Assert.AreEqual(false, data[0].isDir); + } + + [Test] + public async Task GetTest() + { + var destination = "../../../TestData/Data1.txt"; + var pathForFile = "../../../TestData/Data.txt"; + var result = File.ReadAllBytes(pathForFile); + using (var fstream = new FileStream(destination, FileMode.OpenOrCreate)) + { + var data = await _client.Get(pathForFile, fstream, _cancellationToken); + Assert.AreEqual(result.Length, data); + } + + var result2 = File.ReadAllBytes(destination); + Assert.AreEqual(result, result2); + File.Delete(destination); + } + + [Test] + public void ParallelClientConnection() + { + var clients = new Task[2]; + for (var i = 0; i < 2; i++) + { + clients[i] = Task.Run(() => + { + var localClient = new Client("127.0.0.1", 80); + var answer = localClient.List("../../../TestData", _cancellationToken).Result; + Assert.AreEqual("../../../TestData\\Data.txt", answer[0].name); + }); + } + + foreach (var request in clients) + { + request.Wait(); + } + } +} \ No newline at end of file diff --git a/MyFTP/MyFTP.Test/TestData/Data.txt b/MyFTP/MyFTP.Test/TestData/Data.txt new file mode 100644 index 0000000..dc36f61 --- /dev/null +++ b/MyFTP/MyFTP.Test/TestData/Data.txt @@ -0,0 +1 @@ +qwerty \ No newline at end of file diff --git a/MyFTP/MyFTP.sln b/MyFTP/MyFTP.sln new file mode 100644 index 0000000..794b62f --- /dev/null +++ b/MyFTP/MyFTP.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyFTPClient", "MyFTPClient\MyFTPClient.csproj", "{97AFB503-9655-404C-9CE3-DB6CF51BD205}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyFTP.Test", "MyFTP.Test\MyFTP.Test.csproj", "{D4A4CBB3-3517-431E-8B0D-A71D6389E3B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyFTPServer", "MyFTPServer\MyFTPServer.csproj", "{D6B47A97-BA0C-4B6C-9A22-3A3BB8AF6F1E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {97AFB503-9655-404C-9CE3-DB6CF51BD205}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97AFB503-9655-404C-9CE3-DB6CF51BD205}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97AFB503-9655-404C-9CE3-DB6CF51BD205}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97AFB503-9655-404C-9CE3-DB6CF51BD205}.Release|Any CPU.Build.0 = Release|Any CPU + {D4A4CBB3-3517-431E-8B0D-A71D6389E3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4A4CBB3-3517-431E-8B0D-A71D6389E3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4A4CBB3-3517-431E-8B0D-A71D6389E3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4A4CBB3-3517-431E-8B0D-A71D6389E3B8}.Release|Any CPU.Build.0 = Release|Any CPU + {D6B47A97-BA0C-4B6C-9A22-3A3BB8AF6F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6B47A97-BA0C-4B6C-9A22-3A3BB8AF6F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6B47A97-BA0C-4B6C-9A22-3A3BB8AF6F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6B47A97-BA0C-4B6C-9A22-3A3BB8AF6F1E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/MyFTP/MyFTPClient/Client.cs b/MyFTP/MyFTPClient/Client.cs new file mode 100644 index 0000000..3df7009 --- /dev/null +++ b/MyFTP/MyFTPClient/Client.cs @@ -0,0 +1,80 @@ +namespace MyFTPClient; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +/// +/// класс клиента для общения с сервером +/// +public class Client +{ + private readonly int _port; + private readonly string _host; + + public Client(string host, int port ) + { + _host = host; + _port = port; + } + + /// + /// запрос на листинг файлов в папке по пути + /// + public async Task<(string name, bool isDir)[]> List(string path, CancellationToken cancellationToken) + { + using var client = new TcpClient(); + await client.ConnectAsync(_host, _port, cancellationToken); + await using var stream = client.GetStream(); + await using var writer = new StreamWriter(stream); + using var reader = new StreamReader(stream); + await writer.WriteLineAsync($"1 {path}"); + await writer.FlushAsync(); + var info = await reader.ReadLineAsync(); + var infoArray = info.Split(' '); + var size = Convert.ToInt32(infoArray[0]); + if (size == -1) + { + throw new FileNotFoundException(); + } + + var data = new List<(string, bool)>(); + + + + for (int i = 1; i < infoArray.Length; i += 2) + { + + var isDir = Convert.ToBoolean(infoArray[i + 1]); + data.Add((infoArray[i], isDir)); + } + + return data.ToArray(); + } + + /// + /// запрос на скачивание нужного файла + /// + public async Task Get(string path, Stream fileStream, CancellationToken cancellationToken) + { + using var client = new TcpClient(); + await client.ConnectAsync(_host, _port, cancellationToken); + await using var stream = client.GetStream(); + await using var writer = new StreamWriter(stream); + using var reader = new StreamReader(stream); + await writer.WriteLineAsync($"2 {path}"); + await writer.FlushAsync(); + var size = Convert.ToInt32(await reader.ReadLineAsync()); + if (size == -1) + { + throw new FileNotFoundException(); + } + + await stream.CopyToAsync(fileStream, cancellationToken); + + return size; + } +} \ No newline at end of file diff --git a/MyFTP/MyFTPClient/MyFTPClient.csproj b/MyFTP/MyFTPClient/MyFTPClient.csproj new file mode 100644 index 0000000..7b96d01 --- /dev/null +++ b/MyFTP/MyFTPClient/MyFTPClient.csproj @@ -0,0 +1,8 @@ + + + + Exe + net6.0 + + + diff --git a/MyFTP/MyFTPClient/Program.cs b/MyFTP/MyFTPClient/Program.cs new file mode 100644 index 0000000..0c57810 --- /dev/null +++ b/MyFTP/MyFTPClient/Program.cs @@ -0,0 +1,58 @@ +namespace MyFTPClient; + +using System.Threading; +using System; +using System.IO; +using System.Threading.Tasks; + +public class Program +{ + public static async Task Main(string[] args) + { + var client = new Client(args[0], Convert.ToInt32(args[1])); + var token = new CancellationToken(); + Console.WriteLine("1 - List — листинг файлов в директории на сервере\nНапример, 1 ./Test/Files\n"); + Console.WriteLine("2 - Get — скачивание файла с сервера\n2 ./Test/Files/file1.txt\n"); + Console.WriteLine("Введите !exit, чтобы остановить сервер\n"); + Console.WriteLine("Введите команду:"); + var request = Console.ReadLine().Split(' '); + while (request[0] != "!exit" || !token.IsCancellationRequested) + { + if (request[0] == "1" && request.Length == 2) + { + try + { + var response = await client.List(request[1], token); + Console.WriteLine(response.Length); + foreach (var file in response) + { + Console.WriteLine($"{file.Item1} {file.Item2}"); + } + } + catch (FileNotFoundException) + { + Console.WriteLine("-1"); + } + } + else if (request[0] == "2" && request.Length == 2) + { + using var fstream = new FileStream(request[2], FileMode.OpenOrCreate); + try + { + var response = await client.Get(request[1], fstream, token); + Console.WriteLine(response); + } + catch (FileNotFoundException) + { + Console.WriteLine("-1"); + } + } + else + { + Console.WriteLine("Некорректная команда!"); + } + Console.WriteLine("\nВведите команду:"); + request = Console.ReadLine().Split(' '); + } + } +} \ No newline at end of file diff --git a/MyFTP/MyFTPServer/MyFTPServer.csproj b/MyFTP/MyFTPServer/MyFTPServer.csproj new file mode 100644 index 0000000..7b96d01 --- /dev/null +++ b/MyFTP/MyFTPServer/MyFTPServer.csproj @@ -0,0 +1,8 @@ + + + + Exe + net6.0 + + + diff --git a/MyFTP/MyFTPServer/Program.cs b/MyFTP/MyFTPServer/Program.cs new file mode 100644 index 0000000..82349c7 --- /dev/null +++ b/MyFTP/MyFTPServer/Program.cs @@ -0,0 +1,33 @@ +namespace MyFTPServer; + +using System; +using System.Threading.Tasks; + +public class Program +{ + public static async Task Main(string[] args) + { + if (args.Length != 2) + { + Console.WriteLine("Вы не передали параметры или передали неверные."); + return; + } + try + { + var server = new Server(args[0], Convert.ToInt32(args[1])); + var serverStop = server.StartServer(); + Console.WriteLine("Введите !exit, чтобы остановить сервер"); + var command = ""; + while (command != "!exit") + { + command = Console.ReadLine(); + } + server.StopServer(); + await serverStop; + } + catch (ArgumentException) + { + Console.WriteLine("Ошибка!"); + } + } +} diff --git a/MyFTP/MyFTPServer/Server.cs b/MyFTP/MyFTPServer/Server.cs new file mode 100644 index 0000000..8a70355 --- /dev/null +++ b/MyFTP/MyFTPServer/Server.cs @@ -0,0 +1,117 @@ +namespace MyFTPServer; + +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +/// +/// класс сервера для приема запросов от клиента +/// +public class Server +{ + private readonly TcpListener _listener; + private readonly CancellationTokenSource _tokenSource = new (); + + public Server(string host, int port) + { + _listener = new TcpListener(IPAddress.Parse(host), port); + } + + /// + /// старт приема запросов + /// + public async Task StartServer() + { + var task = new List(); + _listener.Start(); + while (!_tokenSource.IsCancellationRequested) + { + var client = await _listener.AcceptTcpClientAsync(); + task.Add(Working(client)); + } + await Task.WhenAll(task); + _listener.Stop(); + } + + /// + /// остановка приема запросов + /// + public void StopServer() + => _tokenSource.Cancel(); + + /// + /// метод для распределения запросов + /// + private async Task Working(TcpClient client) + { + using (client) + { + using var stream = client.GetStream(); + using var reader = new StreamReader(stream); + using var writer = new StreamWriter(stream); + var request = await reader.ReadLineAsync(); + var (command, path) = (request?.Split()[0], request?.Split()[1]); + switch (command) + { + case "1": + await List(writer, path); + break; + case "2": + await Get(writer, path, stream); + break; + case "!exit": + StopServer(); + break; + default: + await writer.WriteAsync("Ваш протокол сломан!"); + break; + } + } + } + + private async Task List(StreamWriter writer, string path) + { + if (!Directory.Exists(path)) + { + await writer.WriteLineAsync("-1"); + return; + } + + var files = Directory.GetFiles(path); + var directories = Directory.GetDirectories(path); + var size = files.Length + directories.Length; + var result = new StringBuilder(); + result.Append(size).ToString(); + foreach (var file in files) + { + result.Append($" {file} false"); + } + + foreach (var dir in directories) + { + result.Append($" {dir} true"); + } + + await writer.WriteLineAsync(size.ToString() + result); + await writer.FlushAsync(); + } + + private async Task Get(StreamWriter writer, string path, NetworkStream stream) + { + if (!File.Exists(path)) + { + await writer.WriteLineAsync("-1"); + return; + } + + var file = new FileStream(path, FileMode.Open); + await writer.WriteLineAsync($"{file.Length} "); + await writer.FlushAsync(); + await file.CopyToAsync(stream); + await writer.FlushAsync(); + } +} \ No newline at end of file diff --git a/MyFTP/global.json b/MyFTP/global.json new file mode 100644 index 0000000..c14434a --- /dev/null +++ b/MyFTP/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0", + "rollForward": "minor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 9809100..d79f319 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,6 @@ +image: Visual Studio 2022 + + build_script: - For /R %%I in (*.sln) do dotnet test %%I test: of \ No newline at end of file