blob: 795d595b8765b29bf9db88382f28af97937971ca [file] [log] [blame]
rootcd436562025-05-08 14:09:19 +00001package tracker;
root0d8b11f2025-05-15 14:10:43 +00002import java.io.File;
rootd4959a82025-05-27 07:07:37 +00003import java.nio.file.Files;
4import java.nio.file.Path;
5import java.nio.file.Paths;
6import java.nio.file.StandardCopyOption;
TRM-codingd5de51e2025-06-08 03:27:01 +08007import java.security.MessageDigest;
rootd4959a82025-05-27 07:07:37 +00008import java.time.LocalDateTime;
TRM-codingd5de51e2025-06-08 03:27:01 +08009import java.util.HashMap;
10import java.util.List;
11import java.util.Map;
12import java.util.UUID;
13import javax.persistence.*;
14import com.dampcake.bencode.Bencode;
15import com.dampcake.bencode.Type;
16import com.querydsl.jpa.impl.JPAUpdateClause;
17import entity.*;
18import entity.config;
TRM-coding508b31f2025-06-09 02:07:14 +080019import java.util.Scanner;
20import java.io.IOException;
root0d8b11f2025-05-15 14:10:43 +000021public class Tracker implements TrackerInterface {
rootff0769a2025-05-18 17:24:41 +000022 private final EntityManagerFactory emf;
rootff0769a2025-05-18 17:24:41 +000023 // 默认构造:产线数据库
24 public Tracker() {
25 config cfg = new config();
26 Map<String,Object> props = new HashMap<>();
27 props.put("javax.persistence.jdbc.url",
28 "jdbc:mysql://" + cfg.SqlURL + "/" + cfg.Database);
29 props.put("javax.persistence.jdbc.user", cfg.SqlUsername);
30 props.put("javax.persistence.jdbc.password", cfg.SqlPassword);
31 this.emf = Persistence.createEntityManagerFactory("myPersistenceUnit", props);
32 }
rootff0769a2025-05-18 17:24:41 +000033 // 测试传入:测试库
34 public Tracker(EntityManagerFactory emf) {
35 this.emf = emf;
36 }
root0d8b11f2025-05-15 14:10:43 +000037 @Override
TRM-codingd5de51e2025-06-08 03:27:01 +080038 public boolean AddUpLoad(String userid, int upload, String infoHash) {
39 long newTotal = upload; // convert to long
root0d8b11f2025-05-15 14:10:43 +000040 EntityManager em = emf.createEntityManager();
41 EntityTransaction tx = em.getTransaction();
TRM-codingd5de51e2025-06-08 03:27:01 +080042 tx.begin();
root0d8b11f2025-05-15 14:10:43 +000043 try {
TRM-codingd5de51e2025-06-08 03:27:01 +080044 // 1) find the seedId by infoHash
TRM-coding83df9e22025-06-09 17:51:05 +080045 System.out.println("DEBUG AddUpLoad: Looking for infoHash: '" + infoHash + "' (length: " + infoHash.length() + ")");
TRM-codingd5de51e2025-06-08 03:27:01 +080046 String seedId = em.createQuery(
47 "SELECT s.seedId FROM SeedHash s WHERE s.infoHash = :ih", String.class)
48 .setParameter("ih", infoHash)
49 .getSingleResult();
50 // 2) sum existing uploads for this user+seed
51 Long sumSoFar = em.createQuery(
52 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid AND t.seedid = :sid",
53 Long.class)
54 .setParameter("uid", userid)
55 .setParameter("sid", seedId)
56 .getSingleResult();
57 long delta = newTotal - sumSoFar;
58 if (delta < 0L) {
59 tx.rollback();
TRM-coding508b31f2025-06-09 02:07:14 +080060 return false; // error: newTotal less than already recorded
TRM-codingd5de51e2025-06-08 03:27:01 +080061 }
62 if (delta == 0L) {
63 tx.rollback();
64 return false; // nothing to do
65 }
66 // 3) persist a new TransRecord with only the delta
67 TransRecord rd = new TransRecord();
68 rd.taskid = UUID.randomUUID().toString();
69 rd.uploaduserid = userid;
70 rd.seedid = seedId;
71 rd.upload = delta;
72 rd.maxupload = newTotal;
73 em.persist(rd);
TRM-coding508b31f2025-06-09 02:07:14 +080074 em.flush();
TRM-codingd5de51e2025-06-08 03:27:01 +080075 // 4) 重新计算用户的总上传,确保与 TransRecord 完全一致
76 Long totalUpload = em.createQuery(
77 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid",
78 Long.class
79 )
80 .setParameter("uid", userid)
81 .getSingleResult();
TRM-coding508b31f2025-06-09 02:07:14 +080082
83 Long PTuploadbefor = em.createQuery(
84 "SELECT t.upload FROM UserPT t WHERE t.userid = :uid",
85 Long.class
86 ).setParameter("uid", userid)
87 .getSingleResult();
88
89
TRM-codingd5de51e2025-06-08 03:27:01 +080090 UserPT user = em.find(UserPT.class, userid);
91 user.upload = totalUpload;
92 em.merge(user);
TRM-coding508b31f2025-06-09 02:07:14 +080093 em.flush();
root0d8b11f2025-05-15 14:10:43 +000094 tx.commit();
TRM-coding508b31f2025-06-09 02:07:14 +080095
96 Long PTuploadafter = em.createQuery(
97 "SELECT t.upload FROM UserPT t WHERE t.userid = :uid",
98 Long.class
99 ).setParameter("uid", userid)
100 .getSingleResult();
101
TRM-coding83df9e22025-06-09 17:51:05 +0800102 System.out.println("------------------------------------------------");
TRM-coding508b31f2025-06-09 02:07:14 +0800103 System.out.printf("thisadd:%d userptsofar:%d userptafter:%d totaluploadnow:%d delta:%d%n",upload, PTuploadbefor,PTuploadafter, totalUpload,delta);
TRM-coding83df9e22025-06-09 17:51:05 +0800104 System.out.println("------------------------------------------------");
TRM-codingd5de51e2025-06-08 03:27:01 +0800105 return false; // success
106 } catch (RuntimeException ex) {
root0d8b11f2025-05-15 14:10:43 +0000107 if (tx.isActive()) tx.rollback();
TRM-codingd5de51e2025-06-08 03:27:01 +0800108 throw ex;
root0d8b11f2025-05-15 14:10:43 +0000109 } finally {
110 em.close();
111 }
112 }
TRM-codingd5de51e2025-06-08 03:27:01 +0800113
root0d8b11f2025-05-15 14:10:43 +0000114 @Override
rootff0769a2025-05-18 17:24:41 +0000115 public boolean ReduceUpLoad(String userid, int upload){
TRM-codingd5de51e2025-06-08 03:27:01 +0800116 long uploadLong = upload; // convert to long
rootff0769a2025-05-18 17:24:41 +0000117 EntityManager em = emf.createEntityManager();
118 EntityTransaction tx = em.getTransaction();
TRM-codingd5de51e2025-06-08 03:27:01 +0800119 tx.begin();
rootff0769a2025-05-18 17:24:41 +0000120 try {
TRM-codingd5de51e2025-06-08 03:27:01 +0800121 // 1) fetch user and ensure enough upload to reduce
122 UserPT user = em.find(UserPT.class, userid);
123 long before = user.upload;
124 if (uploadLong > before) {
125 tx.rollback();
126 return true; // error: cannot reduce more than current total
127 }
128 // 2) subtract
129 user.upload = before - uploadLong;
130 em.merge(user);
131 // (optional) record a negative TransRecord so sums stay in sync
132 TransRecord rd = new TransRecord();
133 rd.taskid = UUID.randomUUID().toString();
134 rd.uploaduserid = userid;
135 rd.seedid = null;
136 rd.upload = -uploadLong;
137 rd.maxupload = user.upload;
138 em.persist(rd);
rootff0769a2025-05-18 17:24:41 +0000139 tx.commit();
TRM-codingd5de51e2025-06-08 03:27:01 +0800140 return false; // success
141 } catch (RuntimeException ex) {
rootff0769a2025-05-18 17:24:41 +0000142 if (tx.isActive()) tx.rollback();
TRM-codingd5de51e2025-06-08 03:27:01 +0800143 throw ex;
rootff0769a2025-05-18 17:24:41 +0000144 } finally {
145 em.close();
146 }
147 }
root0d8b11f2025-05-15 14:10:43 +0000148 @Override
TRM-codingd5de51e2025-06-08 03:27:01 +0800149 public boolean AddDownload(String userid, int download, String infoHash) {
150 long newTotal = download; // convert to long
rootf35409f2025-05-19 04:41:57 +0000151 EntityManager em = emf.createEntityManager();
152 EntityTransaction tx = em.getTransaction();
153 try {
TRM-codingd5de51e2025-06-08 03:27:01 +0800154 // 1. 查 SeedHash
TRM-coding83df9e22025-06-09 17:51:05 +0800155 System.out.println("DEBUG AddDownload: Looking for infoHash: '" + infoHash + "' (length: " + infoHash.length() + ")");
TRM-codingd5de51e2025-06-08 03:27:01 +0800156 TypedQuery<SeedHash> qsh = em.createQuery(
157 "SELECT s FROM SeedHash s WHERE s.infoHash = :h", SeedHash.class);
158 qsh.setParameter("h", infoHash);
159 List<SeedHash> shl = qsh.getResultList();
160 if (shl.isEmpty()) {
161 System.out.println("seed没有被记录");
TRM-coding508b31f2025-06-09 02:07:14 +0800162 return false;
TRM-codingd5de51e2025-06-08 03:27:01 +0800163 }
164 String seedid = shl.get(0).seedId;
165
166 // 2. 统计该用户在该种子上的已有 download
167 TypedQuery<Long> qsum = em.createQuery(
168 "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t " +
169 "WHERE t.seedid = :sid AND t.downloaduserid = :uid", Long.class);
170 qsum.setParameter("sid", seedid);
171 qsum.setParameter("uid", userid);
172 long oldSeedSum = qsum.getSingleResult();
173
174 long diff = newTotal - oldSeedSum;
175 if (diff <= 0) return false;
176
177 System.out.println("AddDownload: 该种子原有总量=" + oldSeedSum + ", 新总量=" + newTotal + ", 增量=" + diff);
178
179 try {
180 tx.begin();
181 // 1. persist 增量记录
182 TransRecord tr = new TransRecord();
183 tr.taskid = UUID.randomUUID().toString();
184 tr.downloaduserid = userid;
185 tr.seedid = seedid;
186 tr.download = diff;
187 tr.maxdownload = newTotal;
188 em.persist(tr);
189
190 // 2. 全表重新累计该用户所有种子的 download,并更新 UserPT.download
191 TypedQuery<Long> qTotal = em.createQuery(
192 "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t WHERE t.downloaduserid = :uid",
193 Long.class
194 )
195 .setParameter("uid", userid);
196 long userTotalDownload = qTotal.getSingleResult();
197 QUserPT quser = QUserPT.userPT;
198 new JPAUpdateClause(em, quser)
199 .where(quser.userid.eq(userid))
200 .set(quser.download, userTotalDownload)
201 .execute();
202
203 tx.commit();
204 return false;
205 } catch (Exception e) {
206 if (tx.isActive()) tx.rollback();
207 return true;
208 } finally {
209 em.close();
210 }
211 } catch (Exception e) {
rootf35409f2025-05-19 04:41:57 +0000212 return true;
rootf35409f2025-05-19 04:41:57 +0000213 }
214 }
root0d8b11f2025-05-15 14:10:43 +0000215 @Override
rootf35409f2025-05-19 04:41:57 +0000216 public boolean ReduceDownload(String userid, int download) {
TRM-codingd5de51e2025-06-08 03:27:01 +0800217 long downloadLong = download; // convert to long
rootf35409f2025-05-19 04:41:57 +0000218 EntityManager em = emf.createEntityManager();
rootf35409f2025-05-19 04:41:57 +0000219 try {
TRM-codingd5de51e2025-06-08 03:27:01 +0800220 // 1. 预检查当前值
221 TypedQuery<Long> qcurr = em.createQuery(
222 "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class);
223 qcurr.setParameter("uid", userid);
224 long current = qcurr.getSingleResult();
225 if (downloadLong > current) {
226 em.close();
TRM-coding508b31f2025-06-09 02:07:14 +0800227 return false;
TRM-codingd5de51e2025-06-08 03:27:01 +0800228 }
229 // 2. 执行减法更新
230 EntityTransaction tx = em.getTransaction();
rootf35409f2025-05-19 04:41:57 +0000231 tx.begin();
232 QUserPT q = QUserPT.userPT;
TRM-codingd5de51e2025-06-08 03:27:01 +0800233 new JPAUpdateClause(em, q)
rootf35409f2025-05-19 04:41:57 +0000234 .where(q.userid.eq(userid))
TRM-codingd5de51e2025-06-08 03:27:01 +0800235 .set(q.download, q.download.subtract(downloadLong))
rootf35409f2025-05-19 04:41:57 +0000236 .execute();
237 tx.commit();
TRM-codingd5de51e2025-06-08 03:27:01 +0800238 return false;
rootf35409f2025-05-19 04:41:57 +0000239 } catch(Exception e) {
rootf35409f2025-05-19 04:41:57 +0000240 return true;
241 } finally {
TRM-codingd5de51e2025-06-08 03:27:01 +0800242 if (em.isOpen()) em.close();
rootf35409f2025-05-19 04:41:57 +0000243 }
244 }
root0d8b11f2025-05-15 14:10:43 +0000245 @Override
rootf35409f2025-05-19 04:41:57 +0000246 public boolean AddMagic(String userid, int magic) {
247 EntityManager em = emf.createEntityManager();
248 EntityTransaction tx = em.getTransaction();
249 try {
250 tx.begin();
251 QUserPT q = QUserPT.userPT;
252 long updated = new JPAUpdateClause(em, q)
253 .where(q.userid.eq(userid))
254 .set(q.magic, q.magic.add(magic))
255 .execute();
256 tx.commit();
257 return updated <= 0;
258 } catch(Exception e) {
259 if (tx.isActive()) tx.rollback();
260 return true;
261 } finally {
262 em.close();
263 }
264 }
root0d8b11f2025-05-15 14:10:43 +0000265 @Override
rootf35409f2025-05-19 04:41:57 +0000266 public boolean ReduceMagic(String userid, int magic) {
267 EntityManager em = emf.createEntityManager();
268 EntityTransaction tx = em.getTransaction();
269 try {
270 tx.begin();
271 QUserPT q = QUserPT.userPT;
272 long updated = new JPAUpdateClause(em, q)
273 .where(q.userid.eq(userid))
274 .set(q.magic, q.magic.subtract(magic))
275 .execute();
276 tx.commit();
277 return updated <= 0;
278 } catch(Exception e) {
279 if (tx.isActive()) tx.rollback();
280 return true;
281 } finally {
282 em.close();
283 }
284 }
rootd4959a82025-05-27 07:07:37 +0000285 @Override
286 public int SaveTorrent(String seedid, File TTorent){
287 try {
288 Path storageDir = Paths.get(config.TORRENT_STORAGE_DIR);
289 if (!Files.exists(storageDir)) {
290 Files.createDirectories(storageDir);
291 }
292 String filename = TTorent.getName();
293 Path target = storageDir.resolve(seedid + "_" + filename);
294 Files.copy(TTorent.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
TRM-codingd5de51e2025-06-08 03:27:01 +0800295
tianruimingebb3dd02025-06-09 05:07:26 +0000296 // Calculate infoHash using ISO_8859_1 encoding method to match qBittorrent
TRM-codingd5de51e2025-06-08 03:27:01 +0800297 String infoHash = null;
298 try {
tianruimingebb3dd02025-06-09 05:07:26 +0000299 infoHash = calculateInfoHashReencoding(target.toFile());
300 System.out.println("InfoHash (ISO_8859_1): " + infoHash);
TRM-codingd5de51e2025-06-08 03:27:01 +0800301 } catch (Exception e) {
302 System.err.println("Warning: could not parse torrent infoHash: " + e.getMessage());
tianruimingebb3dd02025-06-09 05:07:26 +0000303 // Fallback to direct extraction method
304 try {
305 infoHash = calculateInfoHashDirect(target.toFile());
306 System.out.println("InfoHash (Direct): " + infoHash);
307 } catch (Exception e2) {
308 System.err.println("Warning: fallback infoHash calculation also failed: " + e2.getMessage());
309 }
TRM-codingd5de51e2025-06-08 03:27:01 +0800310 }
311
rootd4959a82025-05-27 07:07:37 +0000312 EntityManager em = emf.createEntityManager();
313 EntityTransaction tx = em.getTransaction();
314 try {
315 tx.begin();
316 Seed seed = em.find(Seed.class, seedid);
317 seed.url = target.toString();
318 em.merge(seed);
TRM-codingd5de51e2025-06-08 03:27:01 +0800319
320 // upsert SeedHash only if we have a valid infoHash
321 if (infoHash != null) {
322 SeedHash sh = new SeedHash();
323 sh.seedId = seedid;
324 sh.infoHash = infoHash;
325 em.merge(sh);
326 }
rootd4959a82025-05-27 07:07:37 +0000327 tx.commit();
328 return 0;
329 } catch (Exception e) {
330 if (tx.isActive()) tx.rollback();
331 return 1;
332 } finally {
333 em.close();
334 }
335 } catch (Exception e) {
336 return 1;
337 }
338 }
root0d8b11f2025-05-15 14:10:43 +0000339 @Override
Raveraae06122025-06-05 08:13:35 +0000340 public File GetTTorent(String seedid, String userid) {
rootd4959a82025-05-27 07:07:37 +0000341 EntityManager em = emf.createEntityManager();
342 EntityTransaction tx = em.getTransaction();
343 File file = null;
344 try {
345 Seed seed = em.find(Seed.class, seedid);
346 if (seed == null || seed.url == null) {
347 return null;
348 }
349 file = new File(seed.url);
350 if (!file.exists()) {
351 return null;
352 }
353 tx.begin();
354 SeedDownload sd = new SeedDownload();
355 sd.seedId = seedid;
356 sd.userId = userid;
rootd4959a82025-05-27 07:07:37 +0000357 LocalDateTime now = LocalDateTime.now();
358 sd.downloadStart = now;
359 sd.downloadEnd = now;
360 em.persist(sd);
361 tx.commit();
362 } catch (Exception e) {
363 if (tx.isActive()) tx.rollback();
364 // ignore persistence errors and still return the file
365 } finally {
366 em.close();
367 }
368 return file;
369 }
root0d8b11f2025-05-15 14:10:43 +0000370 @Override
rootf35409f2025-05-19 04:41:57 +0000371 public int AddRecord(TransRecord rd){
372 EntityManager em = emf.createEntityManager();
373 EntityTransaction tx = em.getTransaction();
374 try {
375 tx.begin();
376 em.persist(rd);
377 tx.commit();
TRM-codingd5de51e2025-06-08 03:27:01 +0800378 // 返回1表示插入成功
rootf35409f2025-05-19 04:41:57 +0000379 return 1;
380 } catch (Exception e) {
381 if (tx.isActive()) tx.rollback();
382 return -1;
383 } finally {
384 em.close();
385 }
386 }
tianruimingebb3dd02025-06-09 05:07:26 +0000387
388 /**
389 * Calculate infoHash by extracting the original info dictionary bytes
390 * from the torrent file, rather than re-encoding the parsed data.
391 * This method preserves the original binary representation.
392 */
393 private String calculateInfoHashDirect(File torrentFile) throws Exception {
394 byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
395
396 // Find the info dictionary in the raw torrent data
397 int infoStart = findInfoDictionary(torrentData);
398 if (infoStart == -1) {
399 throw new Exception("Could not find info dictionary in torrent file");
400 }
401
402 // Extract the info dictionary bytes directly from the original torrent
403 byte[] infoBytes = extractInfoBytes(torrentData, infoStart);
404
405 // Debug: print first few bytes of info dict
406 System.out.print("Info dict starts with: ");
407 for (int i = 0; i < Math.min(20, infoBytes.length); i++) {
408 System.out.printf("%02x ", infoBytes[i] & 0xff);
409 }
410 System.out.println();
411
412 // Calculate SHA1 hash
413 MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
414 byte[] digest = sha1.digest(infoBytes);
415
416 // Convert to hex string
417 StringBuilder sb = new StringBuilder();
418 for (byte b : digest) {
419 sb.append(String.format("%02x", b & 0xff));
420 }
421
422 return sb.toString();
423 }
424
425 /**
426 * Correct method using ISO_8859_1 encoding for infohash calculation
427 * This matches qBittorrent's calculation method
428 */
429 private String calculateInfoHashReencoding(File torrentFile) throws Exception {
430 byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
431
432 // Use ISO_8859_1 charset for infohash calculation (as per BitTorrent specification)
433 Bencode bencodeInfoHash = new Bencode(java.nio.charset.StandardCharsets.ISO_8859_1);
434
435 @SuppressWarnings("unchecked")
436 Map<String,Object> meta = bencodeInfoHash.decode(torrentData, Type.DICTIONARY);
437 @SuppressWarnings("unchecked")
438 Map<String,Object> info = (Map<String,Object>) meta.get("info");
439
440 if (info == null) {
441 throw new Exception("No info dictionary found");
442 }
443
444 // Re-encode the info dictionary using ISO_8859_1
445 byte[] infoBytes = bencodeInfoHash.encode(info);
446
447 // Calculate SHA1 hash
448 MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
449 byte[] digest = sha1.digest(infoBytes);
450
451 StringBuilder sb = new StringBuilder();
452 for (byte b : digest) {
453 sb.append(String.format("%02x", b & 0xff));
454 }
455
456 return sb.toString();
457 }
458
459 /**
460 * Find the position of "4:info" in the torrent data
461 */
462 private int findInfoDictionary(byte[] data) {
463 byte[] pattern = "4:info".getBytes();
464
465 for (int i = 0; i <= data.length - pattern.length; i++) {
466 boolean found = true;
467 for (int j = 0; j < pattern.length; j++) {
468 if (data[i + j] != pattern[j]) {
469 found = false;
470 break;
471 }
472 }
473 if (found) {
474 return i;
475 }
476 }
477 return -1;
478 }
479
480 /**
481 * Extract the info dictionary bytes from the original torrent data
482 */
483 private byte[] extractInfoBytes(byte[] data, int infoStart) throws Exception {
484 // Skip "4:info" to get to the actual dictionary content
485 int dictStart = infoStart + 6; // "4:info".length()
486
487 if (dictStart >= data.length || data[dictStart] != 'd') {
488 throw new Exception("Invalid info dictionary format");
489 }
490
491 // Find the matching 'e' that closes the info dictionary
492 int dictEnd = findMatchingEnd(data, dictStart);
493 if (dictEnd == -1) {
494 throw new Exception("Could not find end of info dictionary");
495 }
496
497 // Extract the info dictionary bytes (including 'd' and 'e')
498 int length = dictEnd - dictStart + 1;
499 byte[] infoBytes = new byte[length];
500 System.arraycopy(data, dictStart, infoBytes, 0, length);
501
502 return infoBytes;
503 }
504
505 /**
506 * Find the matching 'e' for a dictionary that starts with 'd'
507 */
508 private int findMatchingEnd(byte[] data, int start) {
509 if (start >= data.length || data[start] != 'd') {
510 return -1;
511 }
512
513 int depth = 0;
514 int i = start;
515
516 while (i < data.length) {
517 byte b = data[i];
518
519 if (b == 'd' || b == 'l') {
520 // Dictionary or list start
521 depth++;
522 i++;
523 } else if (b == 'e') {
524 // Dictionary or list end
525 depth--;
526 if (depth == 0) {
527 return i;
528 }
529 i++;
530 } else if (b == 'i') {
531 // Integer: i<number>e
532 i++; // skip 'i'
533 while (i < data.length && data[i] != 'e') {
534 i++;
535 }
536 if (i < data.length) i++; // skip 'e'
537 } else if (b >= '0' && b <= '9') {
538 // String: <length>:<string>
539 int lengthStart = i;
540 while (i < data.length && data[i] >= '0' && data[i] <= '9') {
541 i++;
542 }
543 if (i < data.length && data[i] == ':') {
544 // Parse length
545 String lengthStr = new String(data, lengthStart, i - lengthStart);
546 int length = Integer.parseInt(lengthStr);
547 i++; // skip ':'
548 i += length; // skip string content
549 } else {
550 // Invalid format
551 return -1;
552 }
553 } else {
554 // Unknown character
555 i++;
556 }
557 }
558
559 return -1;
560 }
TRM-coding83df9e22025-06-09 17:51:05 +0800561
562 /**
563 * 从数据库中查询所有 info_hash(hex 字符串)
564 */
565 public List<String> getAllInfoHashes() {
566 EntityManager em = emf.createEntityManager();
567 try {
568 return em.createQuery(
569 "SELECT sh.infoHash FROM SeedHash sh", String.class
570 ).getResultList();
571 } finally {
572 em.close();
573 }
574 }
root33a7d952025-05-18 17:24:41 +0000575}