tracker完成

Change-Id: Ib69b662cfd24a8a5c7f3a29e6e5b6baa25611e57
diff --git a/src/main/java/com/example/g8backend/controller/AuthController.java b/src/main/java/com/example/g8backend/controller/AuthController.java
index 6cb1ae8..6781dad 100644
--- a/src/main/java/com/example/g8backend/controller/AuthController.java
+++ b/src/main/java/com/example/g8backend/controller/AuthController.java
@@ -70,7 +70,7 @@
 //            return ApiResponse.error(400, "验证码错误");
 //        }
 
-        redisTemplate.delete(registerDTO.getEmail());
+//        redisTemplate.delete(registerDTO.getEmail());
 
         User user = new User();
         user.setUserName(registerDTO.getUserName());
diff --git a/src/main/java/com/example/g8backend/controller/TrackerController.java b/src/main/java/com/example/g8backend/controller/TrackerController.java
index ace7067..9e567a6 100644
--- a/src/main/java/com/example/g8backend/controller/TrackerController.java
+++ b/src/main/java/com/example/g8backend/controller/TrackerController.java
@@ -11,6 +11,13 @@
 import org.springframework.web.bind.annotation.*;
 import com.example.g8backend.service.ITrackerService;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 @RestController
 @RequestMapping("/tracker")
 public class TrackerController {
@@ -19,9 +26,29 @@
 
     @Autowired
     private ITrackerService trackerService;
+    private String getInfoHash(String queryStr) throws UnsupportedEncodingException {
+        Pattern INFO_HASH = Pattern.compile("info_hash=([^&]+)");
+        Matcher matcher = INFO_HASH.matcher(queryStr);
+        StringBuilder hexString = new StringBuilder();
+        if (matcher.find()) {
+            String matchedHash = matcher.group(1);
+            String decodeInfoHash = URLDecoder.decode(matchedHash, "ISO-8859-1");
+            byte[] infoHashBytes = decodeInfoHash.getBytes("ISO-8859-1");
+            for (byte b : infoHashBytes) {
+                String hex = Integer.toHexString(b & 0xFF);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } else {
+            return null;
+        }
+    }
 
     @GetMapping(value = "/announce/{passkey}", produces = "application/x-bittorrent")
-    public ResponseEntity<byte[]> getAnnouncements(
+    public ResponseEntity<String> getAnnouncements(
             HttpServletRequest request,
             @RequestParam("info_hash") String infoHash,
             @RequestParam("peer_id") String peerId,
@@ -31,14 +58,15 @@
             @RequestParam(value = "event", required = false) String event,
             @RequestParam(value = "left", required = false) Double left,
             @RequestParam(value = "compact", required = false) Integer compact,
-            @PathVariable String passkey) {
+            @PathVariable String passkey) throws UnsupportedEncodingException {
 
         logger.info("Announce request received: info_hash={}, peer_id={}, port={}, uploaded={}, downloaded={}, event={}, left={}, compact={}, passkey={}",
                 infoHash, peerId, port, uploaded, downloaded, event, left, compact, passkey);
-
+        String url = request.getQueryString();
         AnnounceRequestDTO requestDTO = new AnnounceRequestDTO();
         requestDTO.setPasskey(passkey);
-        requestDTO.setInfoHash(infoHash);
+        logger.info("Passkey:{}", getInfoHash(url));
+        requestDTO.setInfoHash(getInfoHash(url));
         requestDTO.setPeerId(peerId);
         requestDTO.setPort(port);
         requestDTO.setUploaded(uploaded);
@@ -51,12 +79,14 @@
         requestDTO.setIp(ipAddress);
 
         AnnounceResponseDTO responseDTO = trackerService.handleAnnounce(requestDTO);
-
-        byte[] bencoded = BencodeUtil.encodeAnnounceResponse(responseDTO);
+        Map<String, Object> response = new HashMap<>();
+        response.put("interval", responseDTO.getInterval());
+        response.put("peers", responseDTO.getPeers());
+        String bencodeResponse = BencodeUtil.encodeISO(response);
         return ResponseEntity
                 .ok()
                 .header("Content-Type", "application/x-bittorrent")
-                .body(bencoded);
+                .body(bencodeResponse);
     }
 
     /**
diff --git a/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
index 17561b6..c9cdab9 100644
--- a/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
@@ -24,8 +24,8 @@
     @Resource
     private IUserStatsService userStatsService;
 
-    private final String tracker = "http://localhost:8080/tracker/announce/";
-    //private final String tracker = "http://127.0.0.1:8080/tracker/announce/";
+//    private final String tracker = "http://localhost:8080/tracker/announce/";
+    private final String tracker = "http://192.168.10.3:8080/tracker/announce/";
 
     @Override
     public Torrent handleTorrentUpload(File file, String fileName, Long userId, String passkey) throws IOException {
diff --git a/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
index f3d966f..8c826ef 100644
--- a/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
@@ -67,7 +67,7 @@
         redisTemplate.opsForSet().add(redisKey, peerId);
 
         // 更新用户流量
-        if (!event.equalsIgnoreCase("started")){
+        if (!"started".equalsIgnoreCase(event)) {
             String statKey = "user:peer:" + passkey + ":" + infoHash + ":" + peerId;
             Map<Object, Object> last = redisTemplate.opsForHash().entries(statKey);
 
@@ -93,6 +93,7 @@
             redisTemplate.opsForHash().putAll(statKey, current);
         }
 
+
         // 构造返回 peer 列表
         List<Map<String, Object>> peerList = new ArrayList<>();
         Set<Object> peerIds = redisTemplate.opsForSet().members(redisKey);
diff --git a/src/main/java/com/example/g8backend/util/BencodeUtil.java b/src/main/java/com/example/g8backend/util/BencodeUtil.java
index 4ec9ce1..b669843 100644
--- a/src/main/java/com/example/g8backend/util/BencodeUtil.java
+++ b/src/main/java/com/example/g8backend/util/BencodeUtil.java
@@ -6,44 +6,163 @@
 import java.nio.charset.StandardCharsets;
 import java.util.*;
 
+//package com.bjtu.pt_station.common;
+
+import com.example.g8backend.entity.Peer;
+import com.dampcake.bencode.Bencode;
+import com.dampcake.bencode.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.*;
+
+import java.util.List;
+import java.util.Map;
+
 public class BencodeUtil {
-    public static byte[] encodeAnnounceResponse(AnnounceResponseDTO dto) {
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
+    private static final Logger logger = LoggerFactory.getLogger(BencodeUtil.class);
+    private static final Bencode bencodeUTF8 = new Bencode(StandardCharsets.UTF_8);
+    private static final Bencode bencodeInfoHash = new Bencode(StandardCharsets.ISO_8859_1);
+
+    public static String encodeUTF8(Map<String, Object> map) {
+        byte[] encodedBytes = bencodeUTF8.encode(map);
+        return new String(encodedBytes, StandardCharsets.UTF_8);
+    }
+
+    public static String encodeISO(Map<String, Object> map) {
+        byte[] encodedBytes = bencodeInfoHash.encode(map);
+        return new String(encodedBytes, StandardCharsets.ISO_8859_1);
+    }
+
+    public static Map<String, Object> decodeHash(byte[] torrentBytes) throws IOException {
+        return bencodeInfoHash.decode(torrentBytes, Type.DICTIONARY);
+    }
+
+    public static Map<String, Object> decodeUTF8(byte[] torrentBytes) throws IOException {
+        return bencodeUTF8.decode(torrentBytes, Type.DICTIONARY);
+    }
+
+    public static Map<String, Object> decodeISO(byte[] torrentBytes) throws IOException {
+        return bencodeInfoHash.decode(torrentBytes, Type.DICTIONARY);
+    }
+
+    public static String calInfoHash(MultipartFile torrentFile) throws IOException {
+        byte[] torrentBytes = torrentFile.getBytes();
         try {
-            out.write('d'); // dictionary start
+            Map<String, Object> dict = decodeUTF8(torrentBytes);
+            Map<String, Object> dictInfoHash = decodeISO(torrentBytes);
 
-            writeString(out, "interval");
-            writeInt(out, dto.getInterval());
-
-            writeString(out, "peers");
-            out.write('l'); // list start
-            for (Map<String, Object> peer : dto.getPeers()) {
-                out.write('d');
-                writeString(out, "ip");
-                writeString(out, (String) peer.get("ip"));
-                writeString(out, "port");
-                writeInt(out, ((Number) peer.get("port")).intValue());
-                out.write('e');
+            Object infoObj = dictInfoHash.get("info");
+            if (infoObj == null) {
+                logger.error("Torrent file does not contain 'info' field.");
+                return null;
             }
-            out.write('e'); // list end
 
-            out.write('e'); // dictionary end
-        } catch (Exception e) {
-            throw new RuntimeException("Bencoding failed", e);
+            byte[] infoData = bencodeInfoHash.encode((Map<?, ?>) infoObj);
+            return calculateInfoHash(infoData);
+        } catch (IOException e) {
+            logger.error("Failed to calculate info hash: {}", e.getMessage());
+            throw e;
         }
-        return out.toByteArray();
     }
 
-    private static void writeString(ByteArrayOutputStream out, String str) throws Exception {
-        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
-        out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.UTF_8));
-        out.write(':');
-        out.write(bytes);
+    private static String calculateInfoHash(byte[] bencodedInfo) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            byte[] hashBytes = digest.digest(bencodedInfo);
+
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hashBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("SHA-1 algorithm not available", e);
+        }
     }
 
-    private static void writeInt(ByteArrayOutputStream out, int i) throws Exception {
-        out.write('i');
-        out.write(String.valueOf(i).getBytes(StandardCharsets.UTF_8));
-        out.write('e');
+    public static String convertToString(byte[] bytes) {
+        return new String(bytes, bencodeInfoHash.getCharset());
+    }
+
+    public static Bencode bittorrent() {
+        return bencodeInfoHash;
+    }
+
+    public static Bencode utf8() {
+        return bencodeUTF8;
+    }
+
+    public static String compactPeers(List<Peer> peers) throws UnknownHostException {
+        ByteBuffer buffer = ByteBuffer.allocate(6 * peers.size());
+        for (Peer peer : peers) {
+            for (byte addr : InetAddress.getByName(peer.getIpAddress()).getAddress()) {
+                buffer.put(addr);
+            }
+            int in = peer.getPort();
+            buffer.put((byte) ((in >>> 8) & 0xFF));
+            buffer.put((byte) (in & 0xFF));
+        }
+        return convertToString(buffer.array());
     }
 }
+
+//public class BencodeUtil {
+//    public static byte[] encodeAnnounceResponse(AnnounceResponseDTO dto) {
+//        ByteArrayOutputStream out = new ByteArrayOutputStream();
+//        try {
+//            out.write('d'); // dictionary start
+//
+//            writeString(out, "interval");
+//            writeInt(out, dto.getInterval());
+//
+//            writeString(out, "peers");
+//            out.write('l'); // list start
+//            for (Map<String, Object> peer : dto.getPeers()) {
+//                out.write('d');
+//                writeString(out, "ip");
+//                writeString(out, (String) peer.get("ip"));
+//                writeString(out, "port");
+//                writeInt(out, ((Number) peer.get("port")).intValue());
+//                out.write('e');
+//            }
+//            out.write('e'); // list end
+//
+//            out.write('e'); // dictionary end
+//        } catch (Exception e) {
+//            throw new RuntimeException("Bencoding failed", e);
+//        }
+//        return out.toByteArray();
+//    }
+//
+//    private static void writeString(ByteArrayOutputStream out, String str) throws Exception {
+//        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
+//        out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.UTF_8));
+//        out.write(':');
+//        out.write(bytes);
+//    }
+//
+//    private static void writeInt(ByteArrayOutputStream out, int i) throws Exception {
+//        out.write('i');
+//        out.write(String.valueOf(i).getBytes(StandardCharsets.UTF_8));
+//        out.write('e');
+//    }
+//}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 59e5554..1610fae 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,25 +1,20 @@
-# production environment
 #spring.datasource.password=G812345678#
 #spring.datasource.username=team8
 #spring.datasource.url=jdbc:mysql://202.205.102.121:3306/g8backend
-
-# development environment
 spring.datasource.password=12345678
 spring.datasource.username=root
 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/g8backend
-
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 spring.sql.init.mode=always
-#logging.level.root=DEBUG
 
-server.port=8080
+
+#logging.level.root=DEBUG
 
 mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml
 
 spring.data.redis.host = 127.0.0.1
 
-
-# spring.data.redis.host = redis-server
+//spring.data.redis.host = redis-server
 spring.data.redis.port = 6379
 
 spring.mail.host=smtp.qq.com
@@ -35,3 +30,5 @@
 logging.level.org.springframework.data.redis= DEBUG
 
 spring.security.user.role-prefix=
+server.port=8080
+server.address=0.0.0.0
\ No newline at end of file