...
This commit is contained in:
parent
2ed5957365
commit
5a8e2293ca
@ -67,6 +67,8 @@ class TorrentService : Service() {
|
|||||||
|
|
||||||
// 3. 초기 세션 상태 적용
|
// 3. 초기 세션 상태 적용
|
||||||
updateSessionState()
|
updateSessionState()
|
||||||
|
|
||||||
|
startLightweightUpdater()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkInitialStatus() {
|
private fun checkInitialStatus() {
|
||||||
@ -123,28 +125,96 @@ class TorrentService : Service() {
|
|||||||
* 핵심 제어 로직: 충전 중일 때만 세션을 열고, Wi-Fi 여부에 따라 슬롯 조절
|
* 핵심 제어 로직: 충전 중일 때만 세션을 열고, Wi-Fi 여부에 따라 슬롯 조절
|
||||||
*/
|
*/
|
||||||
private fun updateSessionState() {
|
private fun updateSessionState() {
|
||||||
if (isCharging) {
|
|
||||||
if (session.isPaused) session.resume()
|
if (session.isPaused) session.resume()
|
||||||
|
|
||||||
// Wi-Fi면 5개, 셀룰러면 1개 다운로드 허용
|
serviceScope.launch {
|
||||||
val sp = SettingsPack()
|
val vector = session.swig().get_torrents()
|
||||||
if (isWifiConnected) {
|
// (핸들, 계산된 우선순위 점수)
|
||||||
sp.activeDownloads(5)
|
val torrentsWithMetadata = mutableListOf<kotlin.Pair<TorrentHandle, Int>>()
|
||||||
sp.activeLimit(8)
|
val torrentsWithoutMetadata = mutableListOf<TorrentHandle>()
|
||||||
} else {
|
|
||||||
sp.activeDownloads(1)
|
|
||||||
sp.activeLimit(2)
|
|
||||||
}
|
|
||||||
session.applySettings(sp)
|
|
||||||
println("TorrentService: 충전 중 - 세션 활성화 (Wi-Fi: $isWifiConnected)")
|
|
||||||
} else {
|
|
||||||
if (!session.isPaused) session.pause()
|
|
||||||
|
|
||||||
// 💡 충전 중단 시 타이머 즉시 종료 (배터리 소모 0)
|
val LOW_SPEED_THRESHOLD = 10 * 1024 // 10 KB/s 미만을 '속도 저조'로 판단
|
||||||
updateJob?.cancel()
|
val PENALTY_SCORE = 1000 // 속도가 낮을 경우 부여할 페널티 (대기열 순서보다 큰 값)
|
||||||
println("TorrentService: 충전 중 아님 - 세션 및 업데이트 타이머 일시정지")
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
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.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_dht.swigValue(), true)
|
||||||
sp.setBoolean(settings_pack.bool_types.enable_lsd.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)
|
session.applySettings(sp)
|
||||||
|
|
||||||
@ -175,7 +257,9 @@ class TorrentService : Service() {
|
|||||||
moveToPrivateStorage((alert as TorrentFinishedAlert).handle())
|
moveToPrivateStorage((alert as TorrentFinishedAlert).handle())
|
||||||
refreshTorrentStats()
|
refreshTorrentStats()
|
||||||
}
|
}
|
||||||
AlertType.METADATA_RECEIVED -> (alert as MetadataReceivedAlert).handle().saveResumeData()
|
AlertType.METADATA_RECEIVED -> {
|
||||||
|
(alert as MetadataReceivedAlert).handle().saveResumeData()
|
||||||
|
}
|
||||||
AlertType.SAVE_RESUME_DATA -> {
|
AlertType.SAVE_RESUME_DATA -> {
|
||||||
val ra = alert as SaveResumeDataAlert
|
val ra = alert as SaveResumeDataAlert
|
||||||
saveResumeFile(ra.handle().infoHash().toString(), ra.params())
|
saveResumeFile(ra.handle().infoHash().toString(), ra.params())
|
||||||
@ -188,6 +272,7 @@ class TorrentService : Service() {
|
|||||||
session.start()
|
session.start()
|
||||||
|
|
||||||
restoreExistingDownloads()
|
restoreExistingDownloads()
|
||||||
|
updateTrackers()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var updateJob: Job? = null
|
private var updateJob: Job? = null
|
||||||
@ -197,11 +282,16 @@ class TorrentService : Service() {
|
|||||||
updateJob?.cancel()
|
updateJob?.cancel()
|
||||||
updateJob = serviceScope.launch {
|
updateJob = serviceScope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
|
updateSessionState()
|
||||||
// 충전 중이고 세션이 돌아갈 때만 C++ 엔진에 상태 업데이트 요청
|
// 충전 중이고 세션이 돌아갈 때만 C++ 엔진에 상태 업데이트 요청
|
||||||
if (isCharging && !session.isPaused) {
|
var delayTime = 2000L
|
||||||
|
if (isCharging) {
|
||||||
session.postTorrentUpdates()
|
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 isDownloading = (state.swig() == torrent_status.state_t.downloading.swigValue())
|
||||||
|
|
||||||
val stateText = when {
|
val stateText = when {
|
||||||
!isCharging -> "충전 대기 중"
|
|
||||||
isPaused -> "일시정지"
|
|
||||||
isDownloading -> "다운로드 중"
|
|
||||||
isFinished -> "완료"
|
isFinished -> "완료"
|
||||||
|
!status.hasMetadata() -> "메타데이터 수신 중..."
|
||||||
|
!isCharging -> "충전 대기 중"
|
||||||
|
isDownloading -> "다운로드 중"
|
||||||
|
isPaused -> "일시정지"
|
||||||
else -> "대기 중"
|
else -> "대기 중"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,21 +363,55 @@ class TorrentService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNotification(tasks: List<TorrentTask>) {
|
private fun updateNotification(tasks: List<TorrentTask>) {
|
||||||
if (tasks.isEmpty()) return
|
if (tasks.isEmpty()) {
|
||||||
val activeTask = tasks.firstOrNull { !it.isPaused && it.progress < 100f } ?: tasks.first()
|
notificationManager.cancel(NOTIFICATION_ID)
|
||||||
val currentProgress = activeTask.progress.toInt()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 최적화: 진행률이 변했을 때만 notify 호출
|
// 1. 메타데이터 수신 중인 파일 수
|
||||||
if (currentProgress == lastProgress) return
|
val metadataCount = tasks.count { it.stateText == "메타데이터 수신 중..." }
|
||||||
lastProgress = currentProgress
|
|
||||||
|
|
||||||
notificationBuilder.setContentTitle(if (tasks.size > 1) "${activeTask.name} 외 ${tasks.size - 1}건" else activeTask.name)
|
// 2. 실제 다운로드 중인 파일들 (충전/와이파이 조건으로 활성화된 것들)
|
||||||
.setContentText("${activeTask.stateText}: $currentProgress%")
|
val downloadingTasks = tasks.filter { it.isPaused == false }.filter { it.stateText == "다운로드 중" }
|
||||||
.setProgress(100, currentProgress, false)
|
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())
|
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)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
@ -337,9 +462,18 @@ class TorrentService : Service() {
|
|||||||
val swigParams = libtorrent.parse_magnet_uri(magnetUri, error)
|
val swigParams = libtorrent.parse_magnet_uri(magnetUri, error)
|
||||||
if (error.value() == 0) {
|
if (error.value() == 0) {
|
||||||
swigParams.setSave_path(tempDir.absolutePath)
|
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)
|
session.swig().async_add_torrent(swigParams)
|
||||||
|
|
||||||
|
// 추가 직후 상태 업데이트 호출하여 즉시 반영
|
||||||
|
updateSessionState()
|
||||||
}
|
}
|
||||||
|
updateTrackers()
|
||||||
} catch (e: Exception) { e.printStackTrace() }
|
} catch (e: Exception) { e.printStackTrace() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user