This commit is contained in:
lunaticbum 2026-03-31 11:38:44 +09:00
parent 2ed5957365
commit 5a8e2293ca

View File

@ -67,6 +67,8 @@ class TorrentService : Service() {
// 3. 초기 세션 상태 적용
updateSessionState()
startLightweightUpdater()
}
private fun checkInitialStatus() {
@ -123,28 +125,96 @@ class TorrentService : Service() {
* 핵심 제어 로직: 충전 중일 때만 세션을 열고, Wi-Fi 여부에 따라 슬롯 조절
*/
private fun updateSessionState() {
if (isCharging) {
if (session.isPaused) session.resume()
if (session.isPaused) session.resume()
// Wi-Fi면 5개, 셀룰러면 1개 다운로드 허용
val sp = SettingsPack()
if (isWifiConnected) {
sp.activeDownloads(5)
sp.activeLimit(8)
} else {
sp.activeDownloads(1)
sp.activeLimit(2)
serviceScope.launch {
val vector = session.swig().get_torrents()
// (핸들, 계산된 우선순위 점수)
val torrentsWithMetadata = mutableListOf<kotlin.Pair<TorrentHandle, Int>>()
val torrentsWithoutMetadata = mutableListOf<TorrentHandle>()
val LOW_SPEED_THRESHOLD = 10 * 1024 // 10 KB/s 미만을 '속도 저조'로 판단
val PENALTY_SCORE = 1000 // 속도가 낮을 경우 부여할 페널티 (대기열 순서보다 큰 값)
for (i in 0 until vector.size.toInt()) {
val handle = TorrentHandle(vector.get(i))
if (!handle.isValid) continue
val status = handle.status()
if (!status.hasMetadata()) {
torrentsWithoutMetadata.add(handle)
} else if (!status.isFinished) {
val currentRate = status.downloadPayloadRate()
val basePriority = status.queuePosition()
// 속도가 너무 낮거나 0인 경우 페널티 부여
// 단, 막 시작해서 속도가 측정되지 않은 경우를 위해 추가 로직을 넣을 수 있지만
// 주기적 업데이트(2초)가 되므로 페널티를 받아도 다음 턴에 기회를 잡을 수 있습니다.
val finalScore = if (currentRate < LOW_SPEED_THRESHOLD) {
basePriority + PENALTY_SCORE
} else {
basePriority
}
torrentsWithMetadata.add(handle to finalScore)
}
}
session.applySettings(sp)
println("TorrentService: 충전 중 - 세션 활성화 (Wi-Fi: $isWifiConnected)")
} else {
if (!session.isPaused) session.pause()
// 💡 충전 중단 시 타이머 즉시 종료 (배터리 소모 0)
updateJob?.cancel()
println("TorrentService: 충전 중 아님 - 세션 및 업데이트 타이머 일시정지")
// 1. 메타데이터 미수신: 무조건 유지
torrentsWithoutMetadata.forEach { it.swig().resume() }
// 2. 파일 다운로드: 계산된 점수(finalScore)가 낮은 순으로 정렬
if (isCharging) {
val maxSlots = if (isWifiConnected) 7 else 2
val sortedByPriority = torrentsWithMetadata.sortedBy { it.second }
sortedByPriority.forEachIndexed { index, pair ->
val handle = pair.first
if (index < maxSlots) {
handle.swig().resume()
} else {
handle.pause()
}
}
} else {
// 배터리 모드
torrentsWithMetadata.forEach { it.first.pause() }
}
refreshTorrentStats()
}
}
private val TRACKER_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
/**
* 세계 최신 트래커 리스트를 가져와 현재 모든 토렌트에 주입합니다.
*/
private fun updateTrackers() {
serviceScope.launch(Dispatchers.IO) {
try {
val trackers = java.net.URL(TRACKER_URL).readText()
.split("\n")
.filter { it.isNotBlank() }
val vector = session.swig().get_torrents()
for (i in 0 until vector.size.toInt()) {
val handle = TorrentHandle(vector.get(i))
if (!handle.isValid) continue
// 각 트래커를 토렌트에 추가
trackers.forEach { trackerUrl ->
handle.addTracker(AnnounceEntry(trackerUrl))
}
// 트래커 추가 후 즉시 재요청(Force Reannounce)
handle.forceReannounce()
}
println("TorrentService: ${trackers.size}개의 트래커 주입 완료")
} catch (e: Exception) {
e.printStackTrace()
}
}
refreshTorrentStats() // 상태 변경 후 즉시 UI 갱신
}
private fun initLibTorrent() {
@ -156,6 +226,18 @@ class TorrentService : Service() {
sp.setInteger(settings_pack.int_types.out_enc_policy.swigValue(), settings_pack.enc_policy.pe_enabled.swigValue())
sp.setBoolean(settings_pack.bool_types.enable_dht.swigValue(), true)
sp.setBoolean(settings_pack.bool_types.enable_lsd.swigValue(), true)
sp.setBoolean(settings_pack.bool_types.enable_upnp.swigValue(), true) // 공유기 포트포워딩 자동 설정
sp.setBoolean(settings_pack.bool_types.enable_natpmp.swigValue(), true)
sp.setInteger(settings_pack.int_types.connections_limit.swigValue(), 1000)
sp.setBoolean(settings_pack.bool_types.enable_incoming_utp.swigValue(), true)
sp.setBoolean(settings_pack.bool_types.enable_outgoing_utp.swigValue(), true)
sp.setInteger(settings_pack.int_types.dht_announce_interval.swigValue(), 300)
sp.connectionsLimit(1000)
sp.activeDownloads(10)
sp.activeLimit(10)
session.applySettings(sp)
@ -175,7 +257,9 @@ class TorrentService : Service() {
moveToPrivateStorage((alert as TorrentFinishedAlert).handle())
refreshTorrentStats()
}
AlertType.METADATA_RECEIVED -> (alert as MetadataReceivedAlert).handle().saveResumeData()
AlertType.METADATA_RECEIVED -> {
(alert as MetadataReceivedAlert).handle().saveResumeData()
}
AlertType.SAVE_RESUME_DATA -> {
val ra = alert as SaveResumeDataAlert
saveResumeFile(ra.handle().infoHash().toString(), ra.params())
@ -188,6 +272,7 @@ class TorrentService : Service() {
session.start()
restoreExistingDownloads()
updateTrackers()
}
private var updateJob: Job? = null
@ -197,11 +282,16 @@ class TorrentService : Service() {
updateJob?.cancel()
updateJob = serviceScope.launch {
while (isActive) {
updateSessionState()
// 충전 중이고 세션이 돌아갈 때만 C++ 엔진에 상태 업데이트 요청
if (isCharging && !session.isPaused) {
var delayTime = 2000L
if (isCharging) {
session.postTorrentUpdates()
} else {
delayTime = 10000L
}
delay(2000) // 2초 주기 (원하는 대로 조절 가능)
delay(delayTime)
}
}
}
@ -249,10 +339,11 @@ class TorrentService : Service() {
val isDownloading = (state.swig() == torrent_status.state_t.downloading.swigValue())
val stateText = when {
!isCharging -> "충전 대기 중"
isPaused -> "일시정지"
isDownloading -> "다운로드 중"
isFinished -> "완료"
!status.hasMetadata() -> "메타데이터 수신 중..."
!isCharging -> "충전 대기 중"
isDownloading -> "다운로드 중"
isPaused -> "일시정지"
else -> "대기 중"
}
@ -272,21 +363,55 @@ class TorrentService : Service() {
}
private fun updateNotification(tasks: List<TorrentTask>) {
if (tasks.isEmpty()) return
val activeTask = tasks.firstOrNull { !it.isPaused && it.progress < 100f } ?: tasks.first()
val currentProgress = activeTask.progress.toInt()
if (tasks.isEmpty()) {
notificationManager.cancel(NOTIFICATION_ID)
return
}
// 최적화: 진행률이 변했을 때만 notify 호출
if (currentProgress == lastProgress) return
lastProgress = currentProgress
// 1. 메타데이터 수신 중인 파일 수
val metadataCount = tasks.count { it.stateText == "메타데이터 수신 중..." }
notificationBuilder.setContentTitle(if (tasks.size > 1) "${activeTask.name}${tasks.size - 1}" else activeTask.name)
.setContentText("${activeTask.stateText}: $currentProgress%")
.setProgress(100, currentProgress, false)
// 2. 실제 다운로드 중인 파일들 (충전/와이파이 조건으로 활성화된 것들)
val downloadingTasks = tasks.filter { it.isPaused == false }.filter { it.stateText == "다운로드 중" }
val downloadingCount = downloadingTasks.size
// 3. 다운로드 중인 파일들의 평균 진행률
val averageProgress = if (downloadingTasks.isNotEmpty()) {
downloadingTasks.map { it.progress }.average().toInt()
} else {
0
}
// 4. 전체 다운로드/업로드 속도 (Session에서 직접 가져옴)
val stats = session.stats()
val downloadSpeed = formatSpeed(stats.downloadRate())
val uploadSpeed = formatSpeed(stats.uploadRate())
// 알림 메시지 구성
val title = if (downloadingCount > 0) "${tasks.size} | 다운 ${downloadingCount} | 메타 ${metadataCount} | 대기 ${tasks.size - (downloadingCount + metadataCount)}" else "대기 중"
val content = StringBuilder().apply {
append("평균 다운 진행율 $averageProgress% | ")
append("$downloadSpeed$uploadSpeed")
}.toString()
notificationBuilder
.setContentTitle(title)
.setContentText(content)
.setProgress(100, averageProgress, downloadingCount == 0 && metadataCount > 0) // 메타 수신 중엔 불확정 게이지
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
// 속도 포맷팅 유틸리티
private fun formatSpeed(bytesPerSecond: Long): String {
val kb = bytesPerSecond / 1024.0
return if (kb >= 1024) {
String.format("%.1f MB/s", kb / 1024.0)
} else {
String.format("%.1f KB/s", kb)
}
}
// --- 데이터 관리 및 이동 로직 (기존 유지) ---
@RequiresApi(Build.VERSION_CODES.Q)
@ -337,9 +462,18 @@ class TorrentService : Service() {
val swigParams = libtorrent.parse_magnet_uri(magnetUri, error)
if (error.value() == 0) {
swigParams.setSave_path(tempDir.absolutePath)
swigParams.flags = swigParams.flags.or_(libtorrent.getAuto_managed())
// 수동 제어를 위해 auto_managed 해제
var flags = swigParams.flags
flags = flags.and_(libtorrent.getAuto_managed().inv())
swigParams.flags = flags
session.swig().async_add_torrent(swigParams)
// 추가 직후 상태 업데이트 호출하여 즉시 반영
updateSessionState()
}
updateTrackers()
} catch (e: Exception) { e.printStackTrace() }
}