root | cd43656 | 2025-05-08 14:09:19 +0000 | [diff] [blame] | 1 | package tracker; |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 2 | import java.io.File; |
root | d4959a8 | 2025-05-27 07:07:37 +0000 | [diff] [blame] | 3 | import java.nio.file.Files; |
| 4 | import java.nio.file.Path; |
| 5 | import java.nio.file.Paths; |
| 6 | import java.nio.file.StandardCopyOption; |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 7 | import java.security.MessageDigest; |
root | d4959a8 | 2025-05-27 07:07:37 +0000 | [diff] [blame] | 8 | import java.time.LocalDateTime; |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 9 | import java.util.HashMap; |
| 10 | import java.util.List; |
| 11 | import java.util.Map; |
| 12 | import java.util.UUID; |
| 13 | import javax.persistence.*; |
| 14 | import com.dampcake.bencode.Bencode; |
| 15 | import com.dampcake.bencode.Type; |
| 16 | import com.querydsl.jpa.impl.JPAUpdateClause; |
| 17 | import entity.*; |
| 18 | import entity.config; |
| 19 | |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 20 | public class Tracker implements TrackerInterface { |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 21 | private final EntityManagerFactory emf; |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 22 | // 默认构造:产线数据库 |
| 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 | } |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 32 | // 测试传入:测试库 |
| 33 | public Tracker(EntityManagerFactory emf) { |
| 34 | this.emf = emf; |
| 35 | } |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 36 | @Override |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 37 | public boolean AddUpLoad(String userid, int upload, String infoHash) { |
| 38 | long newTotal = upload; // convert to long |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 39 | EntityManager em = emf.createEntityManager(); |
| 40 | EntityTransaction tx = em.getTransaction(); |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 41 | tx.begin(); |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 42 | try { |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 43 | // 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); |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 83 | tx.commit(); |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 84 | return false; // success |
| 85 | } catch (RuntimeException ex) { |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 86 | if (tx.isActive()) tx.rollback(); |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 87 | throw ex; |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 88 | } finally { |
| 89 | em.close(); |
| 90 | } |
| 91 | } |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 92 | |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 93 | @Override |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 94 | public boolean ReduceUpLoad(String userid, int upload){ |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 95 | long uploadLong = upload; // convert to long |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 96 | EntityManager em = emf.createEntityManager(); |
| 97 | EntityTransaction tx = em.getTransaction(); |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 98 | tx.begin(); |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 99 | try { |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 100 | // 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); |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 118 | tx.commit(); |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 119 | return false; // success |
| 120 | } catch (RuntimeException ex) { |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 121 | if (tx.isActive()) tx.rollback(); |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 122 | throw ex; |
root | ff0769a | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 123 | } finally { |
| 124 | em.close(); |
| 125 | } |
| 126 | } |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 127 | @Override |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 128 | public boolean AddDownload(String userid, int download, String infoHash) { |
| 129 | long newTotal = download; // convert to long |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 130 | EntityManager em = emf.createEntityManager(); |
| 131 | EntityTransaction tx = em.getTransaction(); |
| 132 | try { |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 133 | // 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) { |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 190 | return true; |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 191 | } |
| 192 | } |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 193 | @Override |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 194 | public boolean ReduceDownload(String userid, int download) { |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 195 | long downloadLong = download; // convert to long |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 196 | EntityManager em = emf.createEntityManager(); |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 197 | try { |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 198 | // 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(); |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 209 | tx.begin(); |
| 210 | QUserPT q = QUserPT.userPT; |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 211 | new JPAUpdateClause(em, q) |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 212 | .where(q.userid.eq(userid)) |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 213 | .set(q.download, q.download.subtract(downloadLong)) |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 214 | .execute(); |
| 215 | tx.commit(); |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 216 | return false; |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 217 | } catch(Exception e) { |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 218 | return true; |
| 219 | } finally { |
TRM-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 220 | if (em.isOpen()) em.close(); |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 221 | } |
| 222 | } |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 223 | @Override |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 224 | 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 | } |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 243 | @Override |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 244 | 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 | } |
root | d4959a8 | 2025-05-27 07:07:37 +0000 | [diff] [blame] | 263 | @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-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 273 | |
| 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 | |
root | d4959a8 | 2025-05-27 07:07:37 +0000 | [diff] [blame] | 293 | 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-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 300 | |
| 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 | } |
root | d4959a8 | 2025-05-27 07:07:37 +0000 | [diff] [blame] | 308 | 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 | } |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 320 | @Override |
Raver | aae0612 | 2025-06-05 08:13:35 +0000 | [diff] [blame] | 321 | public File GetTTorent(String seedid, String userid) { |
root | d4959a8 | 2025-05-27 07:07:37 +0000 | [diff] [blame] | 322 | 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; |
root | d4959a8 | 2025-05-27 07:07:37 +0000 | [diff] [blame] | 338 | 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 | } |
root | 0d8b11f | 2025-05-15 14:10:43 +0000 | [diff] [blame] | 351 | @Override |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 352 | 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-coding | d5de51e | 2025-06-08 03:27:01 +0800 | [diff] [blame] | 359 | // 返回1表示插入成功 |
root | f35409f | 2025-05-19 04:41:57 +0000 | [diff] [blame] | 360 | return 1; |
| 361 | } catch (Exception e) { |
| 362 | if (tx.isActive()) tx.rollback(); |
| 363 | return -1; |
| 364 | } finally { |
| 365 | em.close(); |
| 366 | } |
| 367 | } |
root | 33a7d95 | 2025-05-18 17:24:41 +0000 | [diff] [blame] | 368 | } |