修复infohash计算逻辑

Change-Id: I2b82b42e457b9fcc1e6e86a6b1b34ded6d60ffb3
diff --git a/src/main/java/tracker/Tracker.java b/src/main/java/tracker/Tracker.java
index 1b38806..11e0265 100644
--- a/src/main/java/tracker/Tracker.java
+++ b/src/main/java/tracker/Tracker.java
@@ -291,25 +291,20 @@
             Path target = storageDir.resolve(seedid + "_" + filename);
             Files.copy(TTorent.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
 
-            // attempt to parse infoHash, but don't fail if parsing fails
+            // Calculate infoHash using ISO_8859_1 encoding method to match qBittorrent
             String infoHash = null;
             try {
-                byte[] torrentData = Files.readAllBytes(target);
-                Bencode bencode = new Bencode();
-                @SuppressWarnings("unchecked")
-                Map<String,Object> meta = bencode.decode(torrentData, Type.DICTIONARY);
-                @SuppressWarnings("unchecked")
-                Map<String,Object> info = (Map<String,Object>) meta.get("info");
-                byte[] infoBytes = bencode.encode(info);
-                MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-                byte[] digest = sha1.digest(infoBytes);
-                StringBuilder sb = new StringBuilder();
-                for (byte b1 : digest) {
-                    sb.append(String.format("%02x", b1));
-                }
-                infoHash = sb.toString();
+                infoHash = calculateInfoHashReencoding(target.toFile());
+                System.out.println("InfoHash (ISO_8859_1): " + infoHash);
             } catch (Exception e) {
                 System.err.println("Warning: could not parse torrent infoHash: " + e.getMessage());
+                // Fallback to direct extraction method
+                try {
+                    infoHash = calculateInfoHashDirect(target.toFile());
+                    System.out.println("InfoHash (Direct): " + infoHash);
+                } catch (Exception e2) {
+                    System.err.println("Warning: fallback infoHash calculation also failed: " + e2.getMessage());
+                }
             }
 
             EntityManager em = emf.createEntityManager();
@@ -387,4 +382,178 @@
             em.close();
         }
     }
+    
+    /**
+     * Calculate infoHash by extracting the original info dictionary bytes
+     * from the torrent file, rather than re-encoding the parsed data.
+     * This method preserves the original binary representation.
+     */
+    private String calculateInfoHashDirect(File torrentFile) throws Exception {
+        byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
+        
+        // Find the info dictionary in the raw torrent data
+        int infoStart = findInfoDictionary(torrentData);
+        if (infoStart == -1) {
+            throw new Exception("Could not find info dictionary in torrent file");
+        }
+        
+        // Extract the info dictionary bytes directly from the original torrent
+        byte[] infoBytes = extractInfoBytes(torrentData, infoStart);
+        
+        // Debug: print first few bytes of info dict
+        System.out.print("Info dict starts with: ");
+        for (int i = 0; i < Math.min(20, infoBytes.length); i++) {
+            System.out.printf("%02x ", infoBytes[i] & 0xff);
+        }
+        System.out.println();
+        
+        // Calculate SHA1 hash
+        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+        byte[] digest = sha1.digest(infoBytes);
+        
+        // Convert to hex string
+        StringBuilder sb = new StringBuilder();
+        for (byte b : digest) {
+            sb.append(String.format("%02x", b & 0xff));
+        }
+        
+        return sb.toString();
+    }
+    
+    /**
+     * Correct method using ISO_8859_1 encoding for infohash calculation
+     * This matches qBittorrent's calculation method
+     */
+    private String calculateInfoHashReencoding(File torrentFile) throws Exception {
+        byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
+        
+        // Use ISO_8859_1 charset for infohash calculation (as per BitTorrent specification)
+        Bencode bencodeInfoHash = new Bencode(java.nio.charset.StandardCharsets.ISO_8859_1);
+        
+        @SuppressWarnings("unchecked")
+        Map<String,Object> meta = bencodeInfoHash.decode(torrentData, Type.DICTIONARY);
+        @SuppressWarnings("unchecked")
+        Map<String,Object> info = (Map<String,Object>) meta.get("info");
+        
+        if (info == null) {
+            throw new Exception("No info dictionary found");
+        }
+        
+        // Re-encode the info dictionary using ISO_8859_1
+        byte[] infoBytes = bencodeInfoHash.encode(info);
+        
+        // Calculate SHA1 hash
+        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+        byte[] digest = sha1.digest(infoBytes);
+        
+        StringBuilder sb = new StringBuilder();
+        for (byte b : digest) {
+            sb.append(String.format("%02x", b & 0xff));
+        }
+        
+        return sb.toString();
+    }
+    
+    /**
+     * Find the position of "4:info" in the torrent data
+     */
+    private int findInfoDictionary(byte[] data) {
+        byte[] pattern = "4:info".getBytes();
+        
+        for (int i = 0; i <= data.length - pattern.length; i++) {
+            boolean found = true;
+            for (int j = 0; j < pattern.length; j++) {
+                if (data[i + j] != pattern[j]) {
+                    found = false;
+                    break;
+                }
+            }
+            if (found) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    
+    /**
+     * Extract the info dictionary bytes from the original torrent data
+     */
+    private byte[] extractInfoBytes(byte[] data, int infoStart) throws Exception {
+        // Skip "4:info" to get to the actual dictionary content
+        int dictStart = infoStart + 6; // "4:info".length()
+        
+        if (dictStart >= data.length || data[dictStart] != 'd') {
+            throw new Exception("Invalid info dictionary format");
+        }
+        
+        // Find the matching 'e' that closes the info dictionary
+        int dictEnd = findMatchingEnd(data, dictStart);
+        if (dictEnd == -1) {
+            throw new Exception("Could not find end of info dictionary");
+        }
+        
+        // Extract the info dictionary bytes (including 'd' and 'e')
+        int length = dictEnd - dictStart + 1;
+        byte[] infoBytes = new byte[length];
+        System.arraycopy(data, dictStart, infoBytes, 0, length);
+        
+        return infoBytes;
+    }
+    
+    /**
+     * Find the matching 'e' for a dictionary that starts with 'd'
+     */
+    private int findMatchingEnd(byte[] data, int start) {
+        if (start >= data.length || data[start] != 'd') {
+            return -1;
+        }
+        
+        int depth = 0;
+        int i = start;
+        
+        while (i < data.length) {
+            byte b = data[i];
+            
+            if (b == 'd' || b == 'l') {
+                // Dictionary or list start
+                depth++;
+                i++;
+            } else if (b == 'e') {
+                // Dictionary or list end
+                depth--;
+                if (depth == 0) {
+                    return i;
+                }
+                i++;
+            } else if (b == 'i') {
+                // Integer: i<number>e
+                i++; // skip 'i'
+                while (i < data.length && data[i] != 'e') {
+                    i++;
+                }
+                if (i < data.length) i++; // skip 'e'
+            } else if (b >= '0' && b <= '9') {
+                // String: <length>:<string>
+                int lengthStart = i;
+                while (i < data.length && data[i] >= '0' && data[i] <= '9') {
+                    i++;
+                }
+                if (i < data.length && data[i] == ':') {
+                    // Parse length
+                    String lengthStr = new String(data, lengthStart, i - lengthStart);
+                    int length = Integer.parseInt(lengthStr);
+                    i++; // skip ':'
+                    i += length; // skip string content
+                } else {
+                    // Invalid format
+                    return -1;
+                }
+            } else {
+                // Unknown character
+                i++;
+            }
+        }
+        
+        return -1;
+    }
 }
\ No newline at end of file