Advertisement
Guest User

Untitled

a guest
May 29th, 2025
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 32.10 KB | None | 0 0
  1.  
  2. I am trying to implement a decentralized file storage system on ASP and docker. I managed to make a file saving function, it works. But there are problems with the rest of the functions. I managed to implement loading and deleting blocks. But it is not possible to do the same with whole files. I tried different ways, but my queries were looping, or I couldn't get metadata from the workers, or something else. Besides, the exists function doesn't work for me. What is the best way to implement uploading and deleting an entire file, as well as searching for files/blocks?
  3.  
  4. Description of my classes:
  5. ChordNode is a node in the Chord ring that stores the identifier and references to neighbors.
  6. ChordRing – manages the structure of the Chord ring, adds/deletes nodes, and searches for key owners.
  7. FingerEntry – describes one entry in the routing table (finger table) for accelerated search in Chord.
  8. KeyValueController is a controller that works with blocks.
  9. Metadata – describes meta information about the file: name, size, parts, etc.
  10. PartMetadata – contains information about a specific block: its index, size, hash, and storage node.
  11. MetadataService is responsible for storing and managing the metadata of files and their parts.
  12. WorkerController is a worker controller for processing user requests.
  13. WorkerFileStorageService – worker storage
  14.  
  15. Code:
  16.  
  17. Index.html
  18.  
  19.  
  20. <!DOCTYPE html>
  21. <html lang="ru">
  22. <head>
  23. <meta charset="UTF-8">
  24. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  25. <title>Файловая система</title>
  26. <link rel="stylesheet" href="css/styles.css">
  27. </head>
  28. <body>
  29. <div class="container">
  30. <h2>Сохранить файл</h2>
  31. <input type="file" id="fileInput">
  32. <button onclick="saveBlock()">Сохранить</button>
  33. <div class="progress-container" id="uploadProgressContainer">
  34. <div class=" progress-bar" id="uploadProgressBar"></div>
  35. </div>
  36. <p id="uploadStatus"></p>
  37.  
  38. <h2>Получить файл</h2>
  39. <input type="text" id="fileNameDownload" placeholder="Имя файла">
  40. <button onclick="getBlock()">Получить</button>
  41. <div class="progress-container" id="downloadProgressContainer">
  42. <div class="progress-bar" id="downloadProgressBar"></div>
  43. </div>
  44. <p id="downloadStatus"></p>
  45.  
  46. <h2>Проверка файла</h2>
  47. <input type="text" id="fileNameCheck" placeholder="Имя файла">
  48. <button onclick="checkKey()">Проверить</button>
  49. <p id="searchStatus"></p>
  50.  
  51. <h2>Удалить файл</h2>
  52. <input type="text" id="fileNameDelete" placeholder="Имя файла">
  53. <button onclick="deleteKey()">Удалить</button>
  54. <p id="deleteStatus"></p>
  55. </div>
  56. <script>
  57. async function saveBlock() {
  58. const file = document.getElementById('fileInput').files[0];
  59. const status = document.getElementById("uploadStatus");
  60. const progressBar = document.getElementById("uploadProgressBar");
  61.  
  62. if (!file) {
  63. alert("Выберите файл!");
  64. return;
  65. }
  66.  
  67. progressBar.style.width = "0%";
  68. status.innerText = "Загрузка...";
  69.  
  70. const formData = new FormData();
  71. formData.append("file", file); // Имя файла берется из объекта file
  72.  
  73. const xhr = new XMLHttpRequest();
  74. xhr.open("POST", "/api/worker/upload", true);
  75. xhr.upload.onprogress = function (event) {
  76. if (event.lengthComputable) {
  77. const percent = event.loaded / event.total * 100;
  78. progressBar.style.width = percent + "%";
  79. }
  80. };
  81. xhr.onload = () => {
  82. if (xhr.status === 200) {
  83. status.innerText = "Файл сохранен!";
  84. progressBar.style.width = "100%";
  85. } else {
  86. status.innerText = "Ошибка!";
  87. }
  88. };
  89. xhr.onerror = () => {
  90. status.innerText = "Ошибка сети!";
  91. };
  92. xhr.send(formData);
  93. }
  94.  
  95. async function getBlock() {
  96. const fileName = document.getElementById('fileNameDownload').value;
  97. const status = document.getElementById("downloadStatus");
  98. const progressBar = document.getElementById("downloadProgressContainer").querySelector(".progress-bar");
  99.  
  100. if (!fileName) {
  101. alert("Введите имя файла!");
  102. return;
  103. }
  104.  
  105. progressBar.style.width = "0%";
  106. status.innerText = "Загрузка...";
  107.  
  108. const xhr = new XMLHttpRequest();
  109. xhr.open("GET", `/api/worker/download/${encodeURIComponent(fileName)}`, true);
  110. xhr.responseType = "blob";
  111. xhr.onprogress = function (event) {
  112. if (event.lengthComputable) {
  113. const percent = event.loaded / event.total * 100;
  114. progressBar.style.width = percent + "%";
  115. }
  116. };
  117. xhr.onload = function () {
  118. if (xhr.status === 200) {
  119. const blob = xhr.response;
  120. const contentDisposition = xhr.getResponseHeader("Content-Disposition");
  121. let fileName = "downloaded_file";
  122. if (contentDisposition) {
  123. const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
  124. if (match != null && match[1]) {
  125. fileName = match[1].replace(/['"]/g, "");
  126. }
  127. }
  128. const link = document.createElement("a");
  129. link.href = URL.createObjectURL(blob);
  130. link.download = fileName;
  131. document.body.appendChild(link);
  132. link.click();
  133. document.body.removeChild(link);
  134. URL.revokeObjectURL(link.href);
  135. progressBar.style.width = "100%";
  136. status.innerText = "Файл получен!";
  137. } else {
  138. status.innerText = "Файл не найден!";
  139. }
  140. };
  141. xhr.onerror = function () {
  142. status.innerText = "Ошибка получения файла!";
  143. };
  144. xhr.send();
  145. }
  146.  
  147. async function checkKey() {
  148. const fileName = document.getElementById('fileNameCheck').value;
  149. const status = document.getElementById("searchStatus");
  150.  
  151. try {
  152. const response = await fetch(`/api/worker/exists/${encodeURIComponent(fileName)}`);
  153. if (response.ok) {
  154. status.innerText = "Файл найден";
  155. } else if (response.status === 404) {
  156. status.innerText = "Файл не найден";
  157. } else {
  158. status.innerText = "Ошибка при проверке!";
  159. }
  160. } catch (error) {
  161. status.innerText = "Ошибка запроса!";
  162. }
  163. }
  164.  
  165. async function deleteKey() {
  166. const fileName = document.getElementById('fileNameDelete').value;
  167. const status = document.getElementById("deleteStatus");
  168.  
  169. try {
  170. const response = await fetch(`/api/worker/delete/${encodeURIComponent(fileName)}`, {
  171. method: "DELETE"
  172. });
  173. if (response.ok) {
  174. status.innerText = "Файл удалён";
  175. } else if (response.status === 404) {
  176. status.innerText = "Файл не найден!";
  177. } else {
  178. status.innerText = "Ошибка удаления!";
  179. }
  180. } catch (error) {
  181. status.innerText = "Ошибка запроса!";
  182. }
  183. }
  184. </script>
  185. </body>
  186. </html>
  187. 2) Docker-compose.yml:
  188.  
  189. services:
  190. worker1:
  191. container_name: worker1
  192. image: myapp
  193. environment:
  194. - ASPNETCORE_URLS=http://+:5001
  195. - CURRENT_NODE_URL=http://worker1:5001
  196. ports:
  197. - "5001:5001"
  198. networks:
  199. - app_net
  200. volumes:
  201. - worker1_storage:/app/storage
  202. - worker1_metadata:/app/local_metadata
  203. deploy:
  204. restart_policy:
  205. condition: on-failure
  206.  
  207. worker2:
  208. container_name: worker2
  209. image: myapp
  210. environment:
  211. - ASPNETCORE_URLS=http://+:5002
  212. - CURRENT_NODE_URL=http://worker2:5002
  213. ports:
  214. - "5002:5002"
  215. networks:
  216. - app_net
  217. volumes:
  218. - worker2_storage:/app/storage
  219. - worker2_metadata:/app/local_metadata
  220. deploy:
  221. restart_policy:
  222. condition: on-failure
  223.  
  224. worker3:
  225. container_name: worker3
  226. image: myapp
  227. environment:
  228. - ASPNETCORE_URLS=http://+:5003
  229. - CURRENT_NODE_URL=http://worker3:5003
  230. ports:
  231. - "5003:5003"
  232. networks:
  233. - app_net
  234. volumes:
  235. - worker3_storage:/app/storage
  236. - worker3_metadata:/app/local_metadata
  237. deploy:
  238. restart_policy:
  239. condition: on-failure
  240.  
  241. worker4:
  242. container_name: worker4
  243. image: myapp
  244. environment:
  245. - ASPNETCORE_URLS=http://+:5004
  246. - CURRENT_NODE_URL=http://worker4:5004
  247. ports:
  248. - "5004:5004"
  249. networks:
  250. - app_net
  251. volumes:
  252. - worker4_storage:/app/storage
  253. - worker4_metadata:/app/local_metadata
  254. deploy:
  255. restart_policy:
  256. condition: on-failure
  257.  
  258. worker5:
  259. container_name: worker5
  260. image: myapp
  261. environment:
  262. - ASPNETCORE_URLS=http://+:5005
  263. - CURRENT_NODE_URL=http://worker5:5005
  264. ports:
  265. - "5005:5005"
  266. networks:
  267. - app_net
  268. volumes:
  269. - worker5_storage:/app/storage
  270. - worker5_metadata:/app/local_metadata
  271. deploy:
  272. restart_policy:
  273. condition: on-failure
  274.  
  275. networks:
  276. app_net:
  277. driver: bridge
  278.  
  279. volumes:
  280. worker1_storage:
  281. worker2_storage:
  282. worker3_storage:
  283. worker4_storage:
  284. worker5_storage:
  285. worker1_metadata:
  286. worker2_metadata:
  287. worker3_metadata:
  288. worker4_metadata:
  289. worker5_metadata:
  290. 3) ChordNode.cs:
  291.  
  292. using DistributedSystems_Lab4;
  293.  
  294. public class ChordNode
  295. {
  296. public int Id { get; }
  297. public string Url { get; }
  298. public ChordNode? Successor { get; set; }
  299. public ChordNode? Predecessor { get; set; }
  300. public List<FingerEntry> FingerTable { get; set; }
  301. private readonly MetadataService metadataService;
  302.  
  303. // Конструктор
  304. public ChordNode(int id, string url)
  305. {
  306. Id = id;
  307. Url = url;
  308. FingerTable = new List<FingerEntry>();
  309. metadataService = new MetadataService();
  310. }
  311.  
  312. public void AddMetadata(string key, Metadata metadata)
  313. {
  314. metadataService.SaveFileMetadata(key, metadata.Parts);
  315. }
  316.  
  317. public Metadata? GetMetadata(string key)
  318. {
  319. var parts = metadataService.GetFileMetadata(key);
  320. if (parts == null)
  321. return null;
  322.  
  323. return new Metadata
  324. {
  325. FileName = key,
  326. Parts = parts
  327. };
  328. }
  329.  
  330. public void RemoveMetadata(string key)
  331. {
  332. metadataService.DeleteFileMetadata(key);
  333. }
  334.  
  335. public override string ToString() => $"{Url} ({Id})";
  336. }
  337. 4) ChordRing.cs:
  338.  
  339. using DistributedSystems_Lab4;
  340. using System.Collections.Generic;
  341. using System.Linq;
  342. using System.Security.Cryptography;
  343. using System.Text;
  344.  
  345. public class ChordRing
  346. {
  347. private readonly List<ChordNode> nodes = new List<ChordNode>();
  348. private readonly int m = 5; // Количество бит для хэширования
  349.  
  350. public void InitializeFingerTables()
  351. {
  352. foreach (var node in nodes)
  353. {
  354. node.FingerTable.Clear();
  355. for (int i = 0; i < m; i++)
  356. {
  357. int start = (node.Id + (int)Math.Pow(2, i)) % (int)Math.Pow(2, m);
  358. node.FingerTable.Add(new FingerEntry { Start = start, Node = FindNodeForFingerTable(start) });
  359. }
  360. }
  361. }
  362.  
  363. private ChordNode FindNodeForFingerTable(int start)
  364. {
  365. var node = nodes.BinarySearch(new ChordNode(start, ""), Comparer<ChordNode>.Create((a, b) => a.Id.CompareTo(b.Id)));
  366. if (node < 0) node = ~node;
  367. return node < nodes.Count ? nodes[node] : nodes[0];
  368. }
  369.  
  370. // Добавляем узел
  371. public void AddNode(string url)
  372. {
  373. int id = Hash(url);
  374. if (nodes.Any(n => n.Id == id)) return;
  375.  
  376. var newNode = new ChordNode(id, url);
  377. nodes.Add(newNode);
  378. nodes.Sort((a, b) => a.Id.CompareTo(b.Id));
  379. UpdateLinks();
  380. InitializeFingerTables();
  381. }
  382.  
  383. public void RemoveNode(string url)
  384. {
  385. int index = nodes.FindIndex(n => n.Url == url);
  386. if (index >= 0)
  387. {
  388. nodes.RemoveAt(index);
  389. UpdateLinks();
  390. InitializeFingerTables();
  391. }
  392. }
  393.  
  394. private void UpdateLinks()
  395. {
  396. int count = nodes.Count;
  397. for (int i = 0; i < count; i++)
  398. {
  399. var current = nodes[i];
  400. current.Successor = nodes[(i + 1) % count];
  401. current.Predecessor = nodes[(i - 1 + count) % count];
  402. }
  403. }
  404.  
  405. public ChordNode FindResponsibleNode(string key)
  406. {
  407. int keyHash = Hash(key);
  408. var node = nodes.BinarySearch(new ChordNode(keyHash, ""), Comparer<ChordNode>.Create((a, b) => a.Id.CompareTo(b.Id)));
  409. if (node < 0) node = ~node;
  410. return node < nodes.Count ? nodes[node] : nodes[0];
  411. }
  412.  
  413. public void ReplicateMetadata(string key, Metadata metadata)
  414. {
  415. var node = FindResponsibleNode(key);
  416. node.AddMetadata(key, metadata);
  417. node.Successor?.AddMetadata(key, metadata);
  418. node.Predecessor?.AddMetadata(key, metadata);
  419. }
  420.  
  421. public static int Hash(string input)
  422. {
  423. using var sha1 = SHA1.Create();
  424. var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(input));
  425. return Math.Abs(BitConverter.ToInt32(hash, 0));
  426. }
  427.  
  428. public IEnumerable<ChordNode> GetAllNodes() => nodes;
  429. }
  430. 5) FingerEntry.cs:
  431.  
  432. namespace DistributedSystems_Lab4
  433. {
  434. public class FingerEntry
  435. {
  436. public int Start { get; set; }
  437. public ChordNode? Node { get; set; }
  438. }
  439. }
  440. 6) Metadata.cs:
  441.  
  442. namespace DistributedSystems_Lab4
  443. {
  444. public class Metadata
  445. {
  446. public string FileName { get; set; }
  447. public List<PartMetadata> Parts { get; set; } = new List<PartMetadata>();
  448. }
  449. }
  450. 7) PartMetadata.cs:
  451.  
  452. namespace DistributedSystems_Lab4
  453. {
  454. public class PartMetadata
  455. {
  456. public string Chunk { get; set; } = "";
  457. public string PartName { get; set; } = "";
  458. public string WorkerUrl { get; set; } = "";
  459.  
  460. public PartMetadata() { }
  461.  
  462. public PartMetadata(string chunk, string partName, string workerUrl)
  463. {
  464. Chunk = chunk;
  465. PartName = partName;
  466. WorkerUrl = workerUrl;
  467. }
  468. }
  469. }
  470. 8) MetadataService.cs:
  471.  
  472. using System.Text.Json;
  473.  
  474. namespace DistributedSystems_Lab4
  475. {
  476. public class MetadataService
  477. {
  478. private readonly string metadataPath = "/app/local_metadata/";
  479. private Dictionary<string, List<PartMetadata>> fileMetadata = new();
  480.  
  481. public MetadataService()
  482. {
  483. if (!Directory.Exists(metadataPath))
  484. Directory.CreateDirectory(metadataPath);
  485.  
  486. LoadMetadata();
  487. }
  488.  
  489. public void SaveFileMetadata(string key, List<PartMetadata> metadata)
  490. {
  491. LoadMetadata(); // обязательно перед изменением
  492. fileMetadata[key] = metadata;
  493. SaveMetadata();
  494. }
  495.  
  496. public List<PartMetadata>? GetFileMetadata(string key)
  497. {
  498. LoadMetadata();
  499. return fileMetadata.TryGetValue(key, out var metadata) ? metadata : null;
  500. }
  501.  
  502. public void DeleteFileMetadata(string key)
  503. {
  504. LoadMetadata();
  505. if (fileMetadata.ContainsKey(key))
  506. {
  507. fileMetadata.Remove(key);
  508. SaveMetadata();
  509. }
  510. }
  511.  
  512. private void SaveMetadata()
  513. {
  514. var path = Path.Combine(metadataPath, "metadata.json");
  515. var json = JsonSerializer.Serialize(fileMetadata);
  516. File.WriteAllText(path, json);
  517. }
  518.  
  519. private void LoadMetadata()
  520. {
  521. var path = Path.Combine(metadataPath, "metadata.json");
  522. if (File.Exists(path))
  523. {
  524. var json = File.ReadAllText(path);
  525. fileMetadata = JsonSerializer.Deserialize<Dictionary<string, List<PartMetadata>>>(json)
  526. ?? new Dictionary<string, List<PartMetadata>>();
  527. }
  528. else
  529. {
  530. fileMetadata = new Dictionary<string, List<PartMetadata>>();
  531. SaveMetadata(); // Создать пустой
  532. }
  533. }
  534. }
  535. }
  536. 9) WorkerController.cs:
  537.  
  538. using Microsoft.AspNetCore.Mvc;
  539. using Microsoft.Extensions.Logging;
  540. using System.IO;
  541. using System.Net.Http;
  542. using System.Text.RegularExpressions;
  543. using System.Threading.Tasks;
  544. using System.Linq;
  545. using System.Security.Cryptography;
  546. using System.Text.Json;
  547. using System.Collections.Generic;
  548.  
  549. namespace DistributedSystems_Lab4
  550. {
  551. [ApiController]
  552. [Route("api/worker")]
  553. public class WorkerController : ControllerBase
  554. {
  555. private readonly WorkerFileStorageService workerStorage;
  556. private readonly IHttpClientFactory httpClientFactory;
  557. private readonly ILogger logger;
  558.  
  559. public WorkerController(WorkerFileStorageService workerStorage, ILogger<WorkerController> logger, IHttpClientFactory httpClientFactory)
  560. {
  561. this.workerStorage = workerStorage;
  562. this.logger = logger;
  563. this.httpClientFactory = httpClientFactory;
  564. }
  565.  
  566. [HttpPost("upload")]
  567. public async Task<IActionResult> Upload([FromForm] IFormFile file)
  568. {
  569. if (file == null || file.Length == 0)
  570. return BadRequest("Файл пустой");
  571.  
  572. const int blockSize = 5 * 1024 * 1024;
  573. var chord = workerStorage.getChordRing();
  574. var client = httpClientFactory.CreateClient();
  575.  
  576. var allMetadataList = new List<PartMetadata>();
  577. var localMetadataList = new List<PartMetadata>();
  578.  
  579. string currentUrl = Environment.GetEnvironmentVariable("CURRENT_NODE_URL") ?? "unknown";
  580.  
  581. int index = 0;
  582. byte[] buffer = new byte[blockSize];
  583.  
  584. using var stream = file.OpenReadStream();
  585. int bytesRead;
  586. while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
  587. {
  588. var partName = $"{file.FileName}_part_{index}";
  589. var node = chord.FindResponsibleNode(partName);
  590.  
  591. var blockStream = new MemoryStream(buffer, 0, bytesRead);
  592. var content = new MultipartFormDataContent();
  593. content.Add(new StreamContent(blockStream), "file", partName);
  594.  
  595. var response = await client.PutAsync($"{node.Url}/store/{partName}?fileName={file.FileName}", content);
  596. if (response.IsSuccessStatusCode)
  597. {
  598. var meta = new PartMetadata
  599. {
  600. Chunk = ComputeHash(buffer, bytesRead),
  601. PartName = partName,
  602. WorkerUrl = node.Url
  603. };
  604. allMetadataList.Add(meta);
  605.  
  606. if (node.Url == currentUrl)
  607. localMetadataList.Add(meta);
  608. }
  609.  
  610. index++;
  611. }
  612.  
  613. if (localMetadataList.Any())
  614. workerStorage.SaveMetadata(file.FileName, localMetadataList);
  615. else
  616. workerStorage.SaveMetadata(file.FileName, new List<PartMetadata>()); // пустой файл
  617.  
  618. return Ok($"Загружено {allMetadataList.Count} блоков");
  619. }
  620.  
  621. private string ComputeHash(byte[] buffer, int length)
  622. {
  623. using var sha1 = SHA1.Create();
  624. var hash = sha1.ComputeHash(buffer, 0, length);
  625. return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
  626. }
  627.  
  628. [HttpGet("download/{name}")]
  629. public async Task<IActionResult> Download(string name)
  630. {
  631. var client = httpClientFactory.CreateClient();
  632.  
  633. if (name.Contains("part"))
  634. {
  635. var stream = await workerStorage.GetBlockAsync(name);
  636. if (stream != null)
  637. return File(stream, "application/octet-stream", name);
  638.  
  639. var nextUrl = GetNextWorkerUrl();
  640. if (nextUrl != null)
  641. {
  642. var response = await client.GetAsync($"{nextUrl}/api/worker/download/{name}");
  643. if (response.IsSuccessStatusCode)
  644. return File(await response.Content.ReadAsStreamAsync(), "application/octet-stream", name);
  645. }
  646.  
  647. return NotFound("Блок не найден");
  648. }
  649. else
  650. {
  651. var allBlocks = workerStorage.ListBlocks()
  652. .Where(b => b.StartsWith(name + "_part_"))
  653. .OrderBy(b => ExtractPartIndex(b))
  654. .ToList();
  655.  
  656. if (!allBlocks.Any())
  657. {
  658. var nextUrl = GetNextWorkerUrl();
  659. if (nextUrl != null)
  660. {
  661. var response = await client.GetAsync($"{nextUrl}/api/worker/download/{name}");
  662. if (response.IsSuccessStatusCode)
  663. return File(await response.Content.ReadAsStreamAsync(), "application/octet-stream", name);
  664. }
  665.  
  666. return NotFound("Файл не найден");
  667. }
  668.  
  669. var memoryStream = new MemoryStream();
  670. foreach (var part in allBlocks)
  671. {
  672. var stream = await workerStorage.GetBlockAsync(part);
  673. if (stream != null)
  674. await stream.CopyToAsync(memoryStream);
  675. }
  676.  
  677. memoryStream.Position = 0;
  678. return File(memoryStream, "application/octet-stream", name);
  679. }
  680. }
  681.  
  682. [HttpDelete("delete/{name}")]
  683. public async Task<IActionResult> Delete(string name)
  684. {
  685. var client = httpClientFactory.CreateClient();
  686.  
  687. if (name.Contains("part"))
  688. {
  689. if (workerStorage.HasBlock(name))
  690. {
  691. var deleted = workerStorage.DeleteBlock(name);
  692. if (deleted)
  693. return Ok("Блок удалён.");
  694. }
  695.  
  696. var nextUrl = GetNextWorkerUrl();
  697. if (nextUrl != null)
  698. {
  699. var response = await client.DeleteAsync($"{nextUrl}/api/worker/delete/{name}");
  700. if (response.IsSuccessStatusCode)
  701. return Ok("Удалён другим воркером");
  702. }
  703.  
  704. return NotFound("Блок не найден");
  705. }
  706. else
  707. {
  708. var allNodes = GetAllWorkerUrls();
  709. var allParts = new List<PartMetadata>();
  710. foreach (var nodeUrl in allNodes)
  711. {
  712. try
  713. {
  714. var metaResponse = await client.GetAsync($"{nodeUrl}/api/worker/meta/{name}");
  715. if (metaResponse.IsSuccessStatusCode)
  716. {
  717. var json = await metaResponse.Content.ReadAsStringAsync();
  718. var parts = JsonSerializer.Deserialize<List<PartMetadata>>(json);
  719. if (parts != null)
  720. allParts.AddRange(parts);
  721. }
  722. }
  723. catch { }
  724. }
  725.  
  726. bool allDeleted = true;
  727. foreach (var part in allParts)
  728. {
  729. try
  730. {
  731. var del = await client.DeleteAsync($"{part.WorkerUrl}/store/{part.PartName}");
  732. if (!del.IsSuccessStatusCode)
  733. allDeleted = false;
  734. }
  735. catch { allDeleted = false; }
  736. }
  737.  
  738. return allDeleted ? Ok("Удалено") : StatusCode(500, "Удаление частично");
  739. }
  740. }
  741.  
  742. [HttpGet("exists/{name}")]
  743. public IActionResult Exists(string name)
  744. {
  745. return workerStorage.HasBlock(name) ? Ok("Есть") : NotFound("Нет");
  746. }
  747.  
  748. [HttpGet("meta/{name}")]
  749. public IActionResult GetMetadata(string name)
  750. {
  751. var meta = workerStorage.GetMetadata(name);
  752. return meta == null ? NotFound("Нет") : Ok(meta);
  753. }
  754.  
  755. [HttpGet("listFiles")]
  756. public IActionResult List()
  757. {
  758. return Ok(workerStorage.ListBlocks());
  759. }
  760.  
  761. private int ExtractPartIndex(string partName)
  762. {
  763. var match = Regex.Match(partName, @"_part_(\\d+)$");
  764. return match.Success ? int.Parse(match.Groups[1].Value) : 0;
  765. }
  766.  
  767. private string? GetNextWorkerUrl()
  768. {
  769. var current = Environment.GetEnvironmentVariable("CURRENT_NODE_URL");
  770. var all = GetAllWorkerUrls();
  771. int index = Array.IndexOf(all, current);
  772. return index == -1 ? null : all[(index + 1) % all.Length];
  773. }
  774.  
  775. private string[] GetAllWorkerUrls() => new[]
  776. {
  777. "http://worker1:5001",
  778. "http://worker2:5002",
  779. "http://worker3:5003",
  780. "http://worker4:5004",
  781. "http://worker5:5005"
  782. };
  783. }
  784. }
  785. 10) WorkerFileStorageService.cs:
  786.  
  787. using Microsoft.AspNetCore.Http;
  788. using Microsoft.Extensions.Logging;
  789. using System.IO;
  790. using System.Threading.Tasks;
  791. using System.Collections.Generic;
  792. using System.Linq;
  793.  
  794. namespace DistributedSystems_Lab4
  795. {
  796. public class WorkerFileStorageService
  797. {
  798. private readonly ILogger logger;
  799. private readonly MetadataService metadataService;
  800. private readonly string storagePath = "/app/storage/";
  801. private readonly ChordRing chordRing;
  802.  
  803. public WorkerFileStorageService(ILogger<WorkerFileStorageService> logger, MetadataService metadataService)
  804. {
  805. this.logger = logger;
  806. this.metadataService = metadataService;
  807. this.chordRing = new ChordRing();
  808.  
  809. if (!Directory.Exists(storagePath))
  810. Directory.CreateDirectory(storagePath);
  811.  
  812. InitializeChordRing();
  813. }
  814.  
  815. private void InitializeChordRing()
  816. {
  817. var nodeUrls = new[]
  818. {
  819. "http://worker1:5001",
  820. "http://worker2:5002",
  821. "http://worker3:5003",
  822. "http://worker4:5004",
  823. "http://worker5:5005"
  824. };
  825.  
  826. foreach (var url in nodeUrls)
  827. {
  828. chordRing.AddNode(url);
  829. logger.LogInformation($"Добавлен узел в ChordRing: {url}");
  830. }
  831.  
  832. chordRing.InitializeFingerTables();
  833. }
  834.  
  835. public ChordRing getChordRing() => chordRing;
  836.  
  837. public void SaveMetadata(string fileName, List<PartMetadata> parts)
  838. {
  839. metadataService.SaveFileMetadata(fileName, parts);
  840. }
  841.  
  842. public List<PartMetadata>? GetMetadata(string key)
  843. {
  844. return metadataService.GetFileMetadata(key);
  845. }
  846. // перегрузка SaveBlockAsync с fileName
  847. public async Task SaveBlockAsync(string blockName, IFormFile file, string fileName)
  848. {
  849. var path = Path.Combine(storagePath, blockName);
  850. using var stream = new FileStream(path, FileMode.Create);
  851. await file.CopyToAsync(stream);
  852.  
  853. var partMeta = new PartMetadata
  854. {
  855. Chunk = blockName.GetHashCode().ToString(),
  856. PartName = blockName,
  857. WorkerUrl = Environment.GetEnvironmentVariable("CURRENT_NODE_URL") ?? "unknown"
  858. };
  859.  
  860. // Сохраняем по ИМЕНИ ФАЙЛА, а не по имени блока
  861. var existing = metadataService.GetFileMetadata(fileName) ?? new List<PartMetadata>();
  862. existing.Add(partMeta);
  863. metadataService.SaveFileMetadata(fileName, existing);
  864. }
  865.  
  866. public async Task<Stream?> GetBlockAsync(string name)
  867. {
  868. string path = Path.Combine(storagePath, name);
  869. try
  870. {
  871. if (File.Exists(path))
  872. {
  873. return new FileStream(path, FileMode.Open, FileAccess.Read);
  874. }
  875. }
  876. catch (Exception ex)
  877. {
  878. logger.LogError($"Ошибка при загрузке блока {name}: {ex.Message}");
  879. }
  880. return null;
  881. }
  882.  
  883. public bool HasBlock(string name)
  884. {
  885. string path = Path.Combine(storagePath, name);
  886. return File.Exists(path);
  887. }
  888.  
  889. public bool DeleteBlock(string name)
  890. {
  891. string path = Path.Combine(storagePath, name);
  892. if (File.Exists(path))
  893. {
  894. File.Delete(path);
  895. metadataService.DeleteFileMetadata(name);
  896. return true;
  897. }
  898.  
  899. return false;
  900. }
  901.  
  902. public IEnumerable<string> ListBlocks()
  903. {
  904. return Directory.GetFiles(storagePath).Select(Path.GetFileName);
  905. }
  906. }
  907. }
  908. 11) Program.cs:
  909.  
  910. using DistributedSystems_Lab4;
  911. using Microsoft.AspNetCore.Builder;
  912. using Microsoft.AspNetCore.DataProtection;
  913. using Microsoft.AspNetCore.Hosting;
  914. using Microsoft.AspNetCore.Http;
  915. using Microsoft.AspNetCore.Http.Features;
  916. using Microsoft.Extensions.DependencyInjection;
  917. using Microsoft.Extensions.Hosting;
  918. using Microsoft.Extensions.Logging;
  919. using System;
  920.  
  921. var builder = WebApplication.CreateBuilder(args);
  922.  
  923. builder.Logging.ClearProviders();
  924. builder.Logging.AddSimpleConsole(options =>
  925. {
  926. options.IncludeScopes = false;
  927. options.SingleLine = true;
  928. options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
  929. });
  930.  
  931. builder.Services.AddMvc();
  932. builder.Services.Configure<Microsoft.AspNetCore.Http.Features.FormOptions>(options =>
  933. {
  934. options.MultipartBodyLengthLimit = 10L * 1024 * 1024 * 1024; // 10GB
  935. });
  936.  
  937. builder.WebHost.ConfigureKestrel(serverOptions =>
  938. {
  939. serverOptions.Limits.MaxRequestBodySize = 10L * 1024 * 1024 * 1024; // 10GB
  940. });
  941.  
  942. builder.Services.Configure<FormOptions>(o =>
  943. {
  944. o.MultipartBodyLengthLimit = 10L * 1024 * 1024 * 1024; // 50 MB
  945. });
  946. builder.Services.AddControllers();
  947. builder.Services.AddHttpClient();
  948. builder.Services.AddSingleton<WorkerFileStorageService>();
  949. builder.Services.AddSingleton<FingerEntry>();
  950. builder.Services.AddSingleton<MetadataService>();
  951.  
  952. var app = builder.Build();
  953.  
  954. app.Logger.LogInformation("Start");
  955.  
  956. app.MapGet("/", () => Results.Redirect("/index.html"));
  957.  
  958. app.UseStaticFiles();
  959.  
  960. app.UseRouting();
  961. app.UseAuthorization();
  962. app.MapControllers();
  963.  
  964. app.Run();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement