blob: 1ac4d949248076ecab1cc3179538c32a9a06af97 [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;
19
root0d8b11f2025-05-15 14:10:43 +000020public class Tracker implements TrackerInterface {
rootff0769a2025-05-18 17:24:41 +000021 private final EntityManagerFactory emf;
rootff0769a2025-05-18 17:24:41 +000022 // 默认构造:产线数据库
23 public Tracker() {
24 config cfg = new config();
25 Map<String,Object> props = new HashMap<>();
26 props.put("javax.persistence.jdbc.url",
27 "jdbc:mysql://" + cfg.SqlURL + "/" + cfg.Database);
28 props.put("javax.persistence.jdbc.user", cfg.SqlUsername);
29 props.put("javax.persistence.jdbc.password", cfg.SqlPassword);
30 this.emf = Persistence.createEntityManagerFactory("myPersistenceUnit", props);
31 }
rootff0769a2025-05-18 17:24:41 +000032 // 测试传入:测试库
33 public Tracker(EntityManagerFactory emf) {
34 this.emf = emf;
35 }
root0d8b11f2025-05-15 14:10:43 +000036 @Override
TRM-codingd5de51e2025-06-08 03:27:01 +080037 public boolean AddUpLoad(String userid, int upload, String infoHash) {
38 long newTotal = upload; // convert to long
root0d8b11f2025-05-15 14:10:43 +000039 EntityManager em = emf.createEntityManager();
40 EntityTransaction tx = em.getTransaction();
TRM-codingd5de51e2025-06-08 03:27:01 +080041 tx.begin();
root0d8b11f2025-05-15 14:10:43 +000042 try {
TRM-codingd5de51e2025-06-08 03:27:01 +080043 // 1) find the seedId by infoHash
44 String seedId = em.createQuery(
45 "SELECT s.seedId FROM SeedHash s WHERE s.infoHash = :ih", String.class)
46 .setParameter("ih", infoHash)
47 .getSingleResult();
48 // 2) sum existing uploads for this user+seed
49 Long sumSoFar = em.createQuery(
50 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid AND t.seedid = :sid",
51 Long.class)
52 .setParameter("uid", userid)
53 .setParameter("sid", seedId)
54 .getSingleResult();
55 long delta = newTotal - sumSoFar;
56 if (delta < 0L) {
57 tx.rollback();
58 return true; // error: newTotal less than already recorded
59 }
60 if (delta == 0L) {
61 tx.rollback();
62 return false; // nothing to do
63 }
64 // 3) persist a new TransRecord with only the delta
65 TransRecord rd = new TransRecord();
66 rd.taskid = UUID.randomUUID().toString();
67 rd.uploaduserid = userid;
68 rd.seedid = seedId;
69 rd.upload = delta;
70 rd.maxupload = newTotal;
71 em.persist(rd);
72
73 // 4) 重新计算用户的总上传,确保与 TransRecord 完全一致
74 Long totalUpload = em.createQuery(
75 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid",
76 Long.class
77 )
78 .setParameter("uid", userid)
79 .getSingleResult();
80 UserPT user = em.find(UserPT.class, userid);
81 user.upload = totalUpload;
82 em.merge(user);
root0d8b11f2025-05-15 14:10:43 +000083 tx.commit();
TRM-codingd5de51e2025-06-08 03:27:01 +080084 return false; // success
85 } catch (RuntimeException ex) {
root0d8b11f2025-05-15 14:10:43 +000086 if (tx.isActive()) tx.rollback();
TRM-codingd5de51e2025-06-08 03:27:01 +080087 throw ex;
root0d8b11f2025-05-15 14:10:43 +000088 } finally {
89 em.close();
90 }
91 }
TRM-codingd5de51e2025-06-08 03:27:01 +080092
root0d8b11f2025-05-15 14:10:43 +000093 @Override
rootff0769a2025-05-18 17:24:41 +000094 public boolean ReduceUpLoad(String userid, int upload){
TRM-codingd5de51e2025-06-08 03:27:01 +080095 long uploadLong = upload; // convert to long
rootff0769a2025-05-18 17:24:41 +000096 EntityManager em = emf.createEntityManager();
97 EntityTransaction tx = em.getTransaction();
TRM-codingd5de51e2025-06-08 03:27:01 +080098 tx.begin();
rootff0769a2025-05-18 17:24:41 +000099 try {
TRM-codingd5de51e2025-06-08 03:27:01 +0800100 // 1) fetch user and ensure enough upload to reduce
101 UserPT user = em.find(UserPT.class, userid);
102 long before = user.upload;
103 if (uploadLong > before) {
104 tx.rollback();
105 return true; // error: cannot reduce more than current total
106 }
107 // 2) subtract
108 user.upload = before - uploadLong;
109 em.merge(user);
110 // (optional) record a negative TransRecord so sums stay in sync
111 TransRecord rd = new TransRecord();
112 rd.taskid = UUID.randomUUID().toString();
113 rd.uploaduserid = userid;
114 rd.seedid = null;
115 rd.upload = -uploadLong;
116 rd.maxupload = user.upload;
117 em.persist(rd);
rootff0769a2025-05-18 17:24:41 +0000118 tx.commit();
TRM-codingd5de51e2025-06-08 03:27:01 +0800119 return false; // success
120 } catch (RuntimeException ex) {
rootff0769a2025-05-18 17:24:41 +0000121 if (tx.isActive()) tx.rollback();
TRM-codingd5de51e2025-06-08 03:27:01 +0800122 throw ex;
rootff0769a2025-05-18 17:24:41 +0000123 } finally {
124 em.close();
125 }
126 }
root0d8b11f2025-05-15 14:10:43 +0000127 @Override
TRM-codingd5de51e2025-06-08 03:27:01 +0800128 public boolean AddDownload(String userid, int download, String infoHash) {
129 long newTotal = download; // convert to long
rootf35409f2025-05-19 04:41:57 +0000130 EntityManager em = emf.createEntityManager();
131 EntityTransaction tx = em.getTransaction();
132 try {
TRM-codingd5de51e2025-06-08 03:27:01 +0800133 // 1. 查 SeedHash
134 TypedQuery<SeedHash> qsh = em.createQuery(
135 "SELECT s FROM SeedHash s WHERE s.infoHash = :h", SeedHash.class);
136 qsh.setParameter("h", infoHash);
137 List<SeedHash> shl = qsh.getResultList();
138 if (shl.isEmpty()) {
139 System.out.println("seed没有被记录");
140 return true;
141 }
142 String seedid = shl.get(0).seedId;
143
144 // 2. 统计该用户在该种子上的已有 download
145 TypedQuery<Long> qsum = em.createQuery(
146 "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t " +
147 "WHERE t.seedid = :sid AND t.downloaduserid = :uid", Long.class);
148 qsum.setParameter("sid", seedid);
149 qsum.setParameter("uid", userid);
150 long oldSeedSum = qsum.getSingleResult();
151
152 long diff = newTotal - oldSeedSum;
153 if (diff <= 0) return false;
154
155 System.out.println("AddDownload: 该种子原有总量=" + oldSeedSum + ", 新总量=" + newTotal + ", 增量=" + diff);
156
157 try {
158 tx.begin();
159 // 1. persist 增量记录
160 TransRecord tr = new TransRecord();
161 tr.taskid = UUID.randomUUID().toString();
162 tr.downloaduserid = userid;
163 tr.seedid = seedid;
164 tr.download = diff;
165 tr.maxdownload = newTotal;
166 em.persist(tr);
167
168 // 2. 全表重新累计该用户所有种子的 download,并更新 UserPT.download
169 TypedQuery<Long> qTotal = em.createQuery(
170 "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t WHERE t.downloaduserid = :uid",
171 Long.class
172 )
173 .setParameter("uid", userid);
174 long userTotalDownload = qTotal.getSingleResult();
175 QUserPT quser = QUserPT.userPT;
176 new JPAUpdateClause(em, quser)
177 .where(quser.userid.eq(userid))
178 .set(quser.download, userTotalDownload)
179 .execute();
180
181 tx.commit();
182 return false;
183 } catch (Exception e) {
184 if (tx.isActive()) tx.rollback();
185 return true;
186 } finally {
187 em.close();
188 }
189 } catch (Exception e) {
rootf35409f2025-05-19 04:41:57 +0000190 return true;
rootf35409f2025-05-19 04:41:57 +0000191 }
192 }
root0d8b11f2025-05-15 14:10:43 +0000193 @Override
rootf35409f2025-05-19 04:41:57 +0000194 public boolean ReduceDownload(String userid, int download) {
TRM-codingd5de51e2025-06-08 03:27:01 +0800195 long downloadLong = download; // convert to long
rootf35409f2025-05-19 04:41:57 +0000196 EntityManager em = emf.createEntityManager();
rootf35409f2025-05-19 04:41:57 +0000197 try {
TRM-codingd5de51e2025-06-08 03:27:01 +0800198 // 1. 预检查当前值
199 TypedQuery<Long> qcurr = em.createQuery(
200 "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class);
201 qcurr.setParameter("uid", userid);
202 long current = qcurr.getSingleResult();
203 if (downloadLong > current) {
204 em.close();
205 return true;
206 }
207 // 2. 执行减法更新
208 EntityTransaction tx = em.getTransaction();
rootf35409f2025-05-19 04:41:57 +0000209 tx.begin();
210 QUserPT q = QUserPT.userPT;
TRM-codingd5de51e2025-06-08 03:27:01 +0800211 new JPAUpdateClause(em, q)
rootf35409f2025-05-19 04:41:57 +0000212 .where(q.userid.eq(userid))
TRM-codingd5de51e2025-06-08 03:27:01 +0800213 .set(q.download, q.download.subtract(downloadLong))
rootf35409f2025-05-19 04:41:57 +0000214 .execute();
215 tx.commit();
TRM-codingd5de51e2025-06-08 03:27:01 +0800216 return false;
rootf35409f2025-05-19 04:41:57 +0000217 } catch(Exception e) {
rootf35409f2025-05-19 04:41:57 +0000218 return true;
219 } finally {
TRM-codingd5de51e2025-06-08 03:27:01 +0800220 if (em.isOpen()) em.close();
rootf35409f2025-05-19 04:41:57 +0000221 }
222 }
root0d8b11f2025-05-15 14:10:43 +0000223 @Override
rootf35409f2025-05-19 04:41:57 +0000224 public boolean AddMagic(String userid, int magic) {
225 EntityManager em = emf.createEntityManager();
226 EntityTransaction tx = em.getTransaction();
227 try {
228 tx.begin();
229 QUserPT q = QUserPT.userPT;
230 long updated = new JPAUpdateClause(em, q)
231 .where(q.userid.eq(userid))
232 .set(q.magic, q.magic.add(magic))
233 .execute();
234 tx.commit();
235 return updated <= 0;
236 } catch(Exception e) {
237 if (tx.isActive()) tx.rollback();
238 return true;
239 } finally {
240 em.close();
241 }
242 }
root0d8b11f2025-05-15 14:10:43 +0000243 @Override
rootf35409f2025-05-19 04:41:57 +0000244 public boolean ReduceMagic(String userid, int magic) {
245 EntityManager em = emf.createEntityManager();
246 EntityTransaction tx = em.getTransaction();
247 try {
248 tx.begin();
249 QUserPT q = QUserPT.userPT;
250 long updated = new JPAUpdateClause(em, q)
251 .where(q.userid.eq(userid))
252 .set(q.magic, q.magic.subtract(magic))
253 .execute();
254 tx.commit();
255 return updated <= 0;
256 } catch(Exception e) {
257 if (tx.isActive()) tx.rollback();
258 return true;
259 } finally {
260 em.close();
261 }
262 }
rootd4959a82025-05-27 07:07:37 +0000263 @Override
264 public int SaveTorrent(String seedid, File TTorent){
265 try {
266 Path storageDir = Paths.get(config.TORRENT_STORAGE_DIR);
267 if (!Files.exists(storageDir)) {
268 Files.createDirectories(storageDir);
269 }
270 String filename = TTorent.getName();
271 Path target = storageDir.resolve(seedid + "_" + filename);
272 Files.copy(TTorent.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
TRM-codingd5de51e2025-06-08 03:27:01 +0800273
274 // attempt to parse infoHash, but don’t fail if parsing fails
275 String infoHash = null;
276 try {
277 byte[] torrentData = Files.readAllBytes(target);
278 Bencode bencode = new Bencode();
279 @SuppressWarnings("unchecked")
280 Map<String,Object> meta = (Map<String,Object>) bencode.decode(torrentData, Type.DICTIONARY);
281 byte[] infoBytes = bencode.encode((Map<String,Object>) meta.get("info"));
282 MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
283 byte[] digest = sha1.digest(infoBytes);
284 StringBuilder sb = new StringBuilder();
285 for (byte b1 : digest) {
286 sb.append(String.format("%02x", b1));
287 }
288 infoHash = sb.toString();
289 } catch (Exception e) {
290 System.err.println("Warning: could not parse torrent infoHash: " + e.getMessage());
291 }
292
rootd4959a82025-05-27 07:07:37 +0000293 EntityManager em = emf.createEntityManager();
294 EntityTransaction tx = em.getTransaction();
295 try {
296 tx.begin();
297 Seed seed = em.find(Seed.class, seedid);
298 seed.url = target.toString();
299 em.merge(seed);
TRM-codingd5de51e2025-06-08 03:27:01 +0800300
301 // upsert SeedHash only if we have a valid infoHash
302 if (infoHash != null) {
303 SeedHash sh = new SeedHash();
304 sh.seedId = seedid;
305 sh.infoHash = infoHash;
306 em.merge(sh);
307 }
rootd4959a82025-05-27 07:07:37 +0000308 tx.commit();
309 return 0;
310 } catch (Exception e) {
311 if (tx.isActive()) tx.rollback();
312 return 1;
313 } finally {
314 em.close();
315 }
316 } catch (Exception e) {
317 return 1;
318 }
319 }
root0d8b11f2025-05-15 14:10:43 +0000320 @Override
Raveraae06122025-06-05 08:13:35 +0000321 public File GetTTorent(String seedid, String userid) {
rootd4959a82025-05-27 07:07:37 +0000322 EntityManager em = emf.createEntityManager();
323 EntityTransaction tx = em.getTransaction();
324 File file = null;
325 try {
326 Seed seed = em.find(Seed.class, seedid);
327 if (seed == null || seed.url == null) {
328 return null;
329 }
330 file = new File(seed.url);
331 if (!file.exists()) {
332 return null;
333 }
334 tx.begin();
335 SeedDownload sd = new SeedDownload();
336 sd.seedId = seedid;
337 sd.userId = userid;
rootd4959a82025-05-27 07:07:37 +0000338 LocalDateTime now = LocalDateTime.now();
339 sd.downloadStart = now;
340 sd.downloadEnd = now;
341 em.persist(sd);
342 tx.commit();
343 } catch (Exception e) {
344 if (tx.isActive()) tx.rollback();
345 // ignore persistence errors and still return the file
346 } finally {
347 em.close();
348 }
349 return file;
350 }
root0d8b11f2025-05-15 14:10:43 +0000351 @Override
rootf35409f2025-05-19 04:41:57 +0000352 public int AddRecord(TransRecord rd){
353 EntityManager em = emf.createEntityManager();
354 EntityTransaction tx = em.getTransaction();
355 try {
356 tx.begin();
357 em.persist(rd);
358 tx.commit();
TRM-codingd5de51e2025-06-08 03:27:01 +0800359 // 返回1表示插入成功
rootf35409f2025-05-19 04:41:57 +0000360 return 1;
361 } catch (Exception e) {
362 if (tx.isActive()) tx.rollback();
363 return -1;
364 } finally {
365 em.close();
366 }
367 }
root33a7d952025-05-18 17:24:41 +0000368}