添加bt与tracker交互

Change-Id: I1327c2ed89a76bee8e2abeb8cb3014b56357689a
diff --git a/src/main/java/com/pt/service/ResourceService.java b/src/main/java/com/pt/service/ResourceService.java
index 1d0e797..d85315e 100644
--- a/src/main/java/com/pt/service/ResourceService.java
+++ b/src/main/java/com/pt/service/ResourceService.java
@@ -6,16 +6,11 @@
 import com.pt.repository.DownloadRepository;
 import com.pt.repository.ResourceRepository;
 import com.pt.repository.TorrentMetaRepository;
-import com.pt.utils.BencodeUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
 import java.time.LocalDateTime;
 import java.util.List;
-import java.util.Map;
 
 @Service
 public class ResourceService {
@@ -40,9 +35,11 @@
         return resourceRepository.findByAuthor(username);
     }
 
-    // 发布资源时,将种子文件二进制数据保存到数据库
-    public void publishResource(String name, String description, String author, double size) throws Exception {
-        // 保存资源信息到数据库,包括种子文件的二进制数据
+    public void publishResource(String name, String description, String author, double size, byte[] torrentBytes) throws Exception {
+        // 解析并保存torrent元信息
+        TorrentMeta meta = torrentService.parseAndSaveTorrent(torrentBytes);
+
+        // 保存资源信息,并关联torrent信息
         Resource resource = new Resource();
         resource.setName(name);
         resource.setDescription(description);
@@ -50,10 +47,8 @@
         resource.setSize(size);
         resource.setPublishTime(LocalDateTime.now());
 
-        // 生成种子数据 byte[]
-        byte[] torrentData = torrentService.generateTorrentBytes("uploads/" + name);
-        resource.setTorrentData(torrentData);
-
+        resource.setTorrentData(torrentBytes);
+        // 这里可以保存torrent文件路径,或直接存数据库,依据你的设计
         resourceRepository.save(resource);
     }
 
diff --git a/src/main/java/com/pt/service/TorrentService.java b/src/main/java/com/pt/service/TorrentService.java
index dcbee69..1fdb700 100644
--- a/src/main/java/com/pt/service/TorrentService.java
+++ b/src/main/java/com/pt/service/TorrentService.java
@@ -2,77 +2,62 @@
 
 import com.pt.entity.TorrentMeta;
 import com.pt.repository.TorrentMetaRepository;
-import com.pt.utils.BencodeUtils;
+import com.pt.utils.BencodeCodec;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.io.*;
 import java.security.MessageDigest;
 import java.time.LocalDateTime;
-import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 @Service
 public class TorrentService {
 
-    //  Tracker 服务器的 announce 地址
-    private static final String ANNOUNCE_URL = "http://localhost:8080/announce";
-    private static final int PIECE_LENGTH = 256 * 1024;
-
     @Autowired
     private TorrentMetaRepository torrentMetaRepository;
 
-    public byte[] generateTorrentBytes(String sourceFilePath) throws Exception {
-        File sourceFile = new File(sourceFilePath);
-
-        // 构造 info 字典
-        Map<String, Object> infoDict = new LinkedHashMap<>();
-        infoDict.put("name", sourceFile.getName());
-        infoDict.put("length", sourceFile.length());
-        infoDict.put("piece length", PIECE_LENGTH);
-        infoDict.put("pieces", calcPiecesHashes(sourceFile));
-
-        // 构造完整 torrent 字典
-        Map<String, Object> torrentDict = new LinkedHashMap<>();
-        torrentDict.put("announce", ANNOUNCE_URL);
-        torrentDict.put("info", infoDict);
-
-        // 编码成种子数据字节数组
-        byte[] bencodedTorrent = BencodeUtils.encode(torrentDict);
-
-        // 计算 info_hash 并保存到数据库(如果需要的话)
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-        byte[] infoEncoded = BencodeUtils.encode(infoDict);
-        sha1.update(infoEncoded);
-        String infoHash = bytesToHex(sha1.digest());
-
-        TorrentMeta meta = new TorrentMeta();
-        meta.setFilename(sourceFile.getName());
-        meta.setInfoHash(infoHash);
-        meta.setSize(sourceFile.length());
-        meta.setUploadTime(LocalDateTime.now());
-        torrentMetaRepository.save(meta);
-
-        return bencodedTorrent;
-    }
-
-
-    private byte[] calcPiecesHashes(File file) throws Exception {
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-        try (InputStream fis = new FileInputStream(file)) {
-            byte[] buffer = new byte[PIECE_LENGTH];
-            ByteArrayOutputStream piecesBuffer = new ByteArrayOutputStream();
-
-            int read;
-            while ((read = fis.read(buffer)) > 0) {
-                sha1.reset();
-                sha1.update(buffer, 0, read);
-                piecesBuffer.write(sha1.digest());
-            }
-            return piecesBuffer.toByteArray();
+    public TorrentMeta parseAndSaveTorrent(byte[] torrentBytes) throws Exception {
+        Map<String, Object> torrentDict = (Map<String, Object>) BencodeCodec.decode(torrentBytes);
+        if (!torrentDict.containsKey("info")) {
+            throw new IllegalArgumentException("Invalid torrent file: missing 'info' dictionary");
         }
+
+        Map<String, Object> infoDict = (Map<String, Object>) torrentDict.get("info");
+
+        // 计算 info_hash
+        byte[] infoEncoded = BencodeCodec.encode(infoDict);
+        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+        byte[] infoHashBytes = sha1.digest(infoEncoded);
+        String infoHash = bytesToHex(infoHashBytes);
+
+        // 获取文件名,大小等
+        String name = (String) infoDict.get("name");
+        long length = 0;
+        if (infoDict.containsKey("length")) {
+            length = ((Number) infoDict.get("length")).longValue();
+        } else if (infoDict.containsKey("files")) {
+            long totalLength = 0;
+            List<Map<String, Object>> files = (List<Map<String, Object>>) infoDict.get("files");
+            for (Map<String, Object> file : files) {
+                totalLength += ((Number) file.get("length")).longValue();
+            }
+            length = totalLength;
+        }
+
+        // 保存到数据库
+        TorrentMeta meta = new TorrentMeta();
+        meta.setFilename(name);
+        meta.setInfoHash(infoHash);
+        meta.setSize(length);
+        meta.setUploadTime(LocalDateTime.now());
+        meta.setTorrentData(torrentBytes);
+
+        torrentMetaRepository.save(meta);
+        return meta;
     }
 
+
     private String bytesToHex(byte[] bytes) {
         StringBuilder sb = new StringBuilder();
         for (byte b : bytes) {
@@ -80,4 +65,4 @@
         }
         return sb.toString();
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/pt/service/TrackerService.java b/src/main/java/com/pt/service/TrackerService.java
new file mode 100644
index 0000000..7167ff3
--- /dev/null
+++ b/src/main/java/com/pt/service/TrackerService.java
@@ -0,0 +1,76 @@
+package com.pt.service;
+
+import com.pt.entity.TorrentMeta;
+import com.pt.repository.TorrentMetaRepository;
+import com.pt.utils.BencodeCodec;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+@Service
+public class TrackerService {
+
+    private final Map<String, List<PeerInfo>> torrentPeers = new ConcurrentHashMap<>();
+
+    @Autowired
+    private TorrentMetaRepository torrentMetaRepository;
+
+    public byte[] handleAnnounce(Map<String, String[]> params, String ipAddress) {
+        try {
+            if (!params.containsKey("info_hash") || !params.containsKey("peer_id") || !params.containsKey("port")) {
+                return BencodeCodec.encode(Map.of("failure reason", "Missing required parameters"));
+            }
+
+            String infoHash = decodeParam(params.get("info_hash")[0]);
+            TorrentMeta meta = torrentMetaRepository.findByInfoHash(infoHash);
+            if (meta == null) {
+                return BencodeCodec.encode(Map.of("failure reason", "Invalid info_hash"));
+            }
+
+            String peerId = decodeParam(params.get("peer_id")[0]);
+            int port = Integer.parseInt(params.get("port")[0]);
+
+            PeerInfo peer = new PeerInfo(ipAddress, port, peerId);
+
+            torrentPeers.computeIfAbsent(infoHash, k -> new CopyOnWriteArrayList<>());
+            List<PeerInfo> peers = torrentPeers.get(infoHash);
+
+            boolean exists = peers.stream().anyMatch(p -> p.peerId.equals(peerId));
+            if (!exists) {
+                peers.add(peer);
+            }
+
+            List<String> ips = peers.stream().map(p -> p.ip).toList();
+            List<Integer> ports = peers.stream().map(p -> p.port).toList();
+            byte[] peerBytes = BencodeCodec.buildCompactPeers(ips, ports);
+
+            return BencodeCodec.buildTrackerResponse(1800, peerBytes);
+        } catch (Exception e) {
+            return BencodeCodec.encode(Map.of("failure reason", "Internal server error"));
+        }
+    }
+
+    private String decodeParam(String raw) {
+        return new String(raw.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
+    }
+
+    private static class PeerInfo {
+        String ip;
+        int port;
+        String peerId;
+
+        public PeerInfo(String ip, int port, String peerId) {
+            this.ip = ip;
+            this.port = port;
+            this.peerId = peerId;
+        }
+    }
+}
+