...
This commit is contained in:
parent
5052d5cf52
commit
33a4674a82
@ -379,7 +379,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const shouldExclude = excludedStrings.some(str => currentUrl.includes(str));
|
||||
|
||||
if (port && !shouldExclude) {
|
||||
toast("connect port on " + location.href);
|
||||
// toast("connect port on " + location.href);
|
||||
time1 = setTimeout(autoScrollAndSave(false), 3500);
|
||||
}
|
||||
})
|
||||
|
||||
@ -59,6 +59,7 @@ import bums.lunatic.launcher.receiver.NLService
|
||||
import bums.lunatic.launcher.receiver.SmsReceiver
|
||||
import bums.lunatic.launcher.settings.SettingsActivity
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.TorrentService
|
||||
import bums.lunatic.launcher.workers.UsageLogType
|
||||
import bums.lunatic.launcher.workers.UsageUpdateType
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
@ -75,6 +76,7 @@ import org.mozilla.geckoview.GeckoRuntime
|
||||
import org.mozilla.geckoview.GeckoRuntimeSettings
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import kotlin.jvm.java
|
||||
|
||||
|
||||
open class LauncherActivity : CommonActivity() {
|
||||
@ -379,8 +381,14 @@ open class LauncherActivity : CommonActivity() {
|
||||
// Blog.LOGE("failed to initialize youtubedl-android", e)
|
||||
// }
|
||||
|
||||
val intent = Intent(this, ForeGroundService::class.java)
|
||||
this.startForegroundService(intent)
|
||||
Intent(this, ForeGroundService::class.java).apply {
|
||||
startForegroundService(intent)
|
||||
}
|
||||
|
||||
Intent(this, TorrentService::class.java).apply {
|
||||
startForegroundService(intent)
|
||||
}
|
||||
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
|
||||
@ -1,36 +1,36 @@
|
||||
package bums.lunatic.launcher.helpers
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
|
||||
class ServiceWatchdogWorker(
|
||||
private val context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : Worker(context, workerParams) {
|
||||
|
||||
companion object{
|
||||
val TAG = "ServiceWatchdogWorker"
|
||||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
val isServiceRunning = isServiceRunning(ForeGroundService::class.java)
|
||||
if (!isServiceRunning) {
|
||||
val intent = Intent(context, ForeGroundService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
fun isServiceRunning(serviceClass: Class<*>): Boolean {
|
||||
val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
return manager.getRunningServices(Int.MAX_VALUE)
|
||||
.any { it.service.className == serviceClass.name }
|
||||
}
|
||||
}
|
||||
//package bums.lunatic.launcher.helpers
|
||||
//
|
||||
//import android.app.ActivityManager
|
||||
//import android.content.Context
|
||||
//import android.content.Intent
|
||||
//import android.os.Build
|
||||
//import androidx.work.Worker
|
||||
//import androidx.work.WorkerParameters
|
||||
//
|
||||
//class ServiceWatchdogWorker(
|
||||
// private val context: Context,
|
||||
// workerParams: WorkerParameters
|
||||
//) : Worker(context, workerParams) {
|
||||
//
|
||||
// companion object{
|
||||
// val TAG = "ServiceWatchdogWorker"
|
||||
// }
|
||||
//
|
||||
// override fun doWork(): Result {
|
||||
// val isServiceRunning = isServiceRunning(ForeGroundService::class.java)
|
||||
// if (!isServiceRunning) {
|
||||
// val intent = Intent(context, ForeGroundService::class.java)
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// context.startForegroundService(intent)
|
||||
// } else {
|
||||
// context.startService(intent)
|
||||
// }
|
||||
// }
|
||||
// return Result.success()
|
||||
// }
|
||||
// fun isServiceRunning(serviceClass: Class<*>): Boolean {
|
||||
// val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
// return manager.getRunningServices(Int.MAX_VALUE)
|
||||
// .any { it.service.className == serviceClass.name }
|
||||
// }
|
||||
//}
|
||||
|
||||
@ -618,15 +618,15 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
|
||||
var toast : Toast? = null
|
||||
private fun Context.toast(msg: String) {
|
||||
val biggerText = SpannableStringBuilder(msg)
|
||||
biggerText.setSpan(RelativeSizeSpan(1.6f), 0, msg.length, 0)
|
||||
val view: View = inflate(this, R.layout.simple_toast, null)
|
||||
view.findViewById<TextView>(R.id.text).text = biggerText
|
||||
if (toast==null) {
|
||||
toast = Toast(this)
|
||||
toast?.duration = Toast.LENGTH_SHORT
|
||||
val biggerText = SpannableStringBuilder(msg)
|
||||
biggerText.setSpan(RelativeSizeSpan(1.6f), 0, msg.length, 0)
|
||||
val view: View = inflate(this, R.layout.simple_toast, null)
|
||||
view.findViewById<TextView>(R.id.text).text = biggerText
|
||||
toast?.view = view
|
||||
}
|
||||
toast?.view = view
|
||||
toast?.show()
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.view.LayoutInflater
|
||||
@ -70,9 +71,18 @@ class TorrentListFragment : BottomSheetDialogFragment() {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// 프래그먼트가 보일 때 서비스 바인딩
|
||||
Intent(requireContext(), TorrentService::class.java).also { intent ->
|
||||
requireContext().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
val intent = Intent(requireContext(), TorrentService::class.java)
|
||||
|
||||
// 💡 1. 핵심: UI가 닫혀도 서비스가 죽지 않도록 독립적인 생명을 부여합니다.
|
||||
// (이미 서비스가 켜져 있다면 onStartCommand만 가볍게 한 번 더 호출되므로 안전합니다.)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
requireContext().startForegroundService(intent)
|
||||
} else {
|
||||
requireContext().startService(intent)
|
||||
}
|
||||
|
||||
// 💡 2. 그 다음 UI 통신을 위해 바인딩을 맺습니다.
|
||||
requireContext().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
||||
@ -23,9 +23,11 @@ import android.app.*
|
||||
import android.content.*
|
||||
import android.os.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import bums.lunatic.launcher.home.toast
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import com.frostwire.jlibtorrent.swig.error_code
|
||||
import com.frostwire.jlibtorrent.swig.libtorrent
|
||||
import com.frostwire.jlibtorrent.swig.settings_pack
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.NonCancellable.isActive
|
||||
@ -51,6 +53,10 @@ class TorrentService : Service() {
|
||||
// 경로 설정
|
||||
private val tempDir by lazy { File(getExternalFilesDir(null), "temp_torrents").apply { mkdirs() } }
|
||||
private val resumeDir by lazy { File(getExternalFilesDir(null), "resume_data").apply { mkdirs() } }
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||
private val NOTIFICATION_ID = 101
|
||||
private var hasActiveTorrents = false // 자동 종료를 위한 플래그
|
||||
|
||||
inner class TorrentBinder : Binder() {
|
||||
fun getService(): TorrentService = this@TorrentService
|
||||
@ -72,8 +78,20 @@ class TorrentService : Service() {
|
||||
private fun startPolling() {
|
||||
serviceScope.launch {
|
||||
while (isActive) {
|
||||
// 1. SessionManager에서 SWIG 원시 객체(vector)로 핸들 목록을 가져옵니다.
|
||||
val vector = session.swig().get_torrents()
|
||||
val taskCount = vector.size.toInt()
|
||||
|
||||
// 💡 1. 자동 종료 로직
|
||||
if (taskCount > 0) {
|
||||
hasActiveTorrents = true // 토렌트가 1개 이상 활성화된 적이 있음을 마킹
|
||||
} else if (hasActiveTorrents && taskCount == 0) {
|
||||
// 활성화된 적이 있었는데 0개가 되었다면 (모두 완료되어 이동되었거나 삭제됨)
|
||||
println("TorrentService: 모든 작업이 완료되어 서비스를 자동 종료합니다.")
|
||||
stopForeground(true) // 💡 상단바 알림 즉시 제거
|
||||
stopSelf() // 💡 서비스 스스로 종료 (메모리 해제)
|
||||
break // 무한 루프 탈출
|
||||
}
|
||||
// 1. SessionManager에서 SWIG 원시 객체(vector)로 핸들 목록을 가져옵니다.
|
||||
val tasks = mutableListOf<TorrentTask>()
|
||||
|
||||
// 2. 각 핸들(TorrentHandle)을 순회하며 데이터를 추출합니다.
|
||||
@ -119,8 +137,12 @@ class TorrentService : Service() {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
_torrentTasks.value = tasks
|
||||
|
||||
// 💡 2. UI 알림 갱신 로직
|
||||
if (tasks.isNotEmpty()) {
|
||||
updateNotification(tasks)
|
||||
}
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
@ -165,6 +187,27 @@ class TorrentService : Service() {
|
||||
|
||||
private fun initLibTorrent() {
|
||||
session = SessionManager()
|
||||
// 💡 속도 최적화를 위한 세팅 적용
|
||||
val sp = SettingsPack()
|
||||
|
||||
// [1] 통신사(ISP) 토렌트 트래픽 제한 우회 (강력 권장)
|
||||
// 패킷을 암호화하여 통신사가 토렌트 다운로드 중인지 모르게 만듭니다.
|
||||
sp.setInteger(settings_pack.int_types.in_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())
|
||||
|
||||
// [2] 마그넷 피어 탐색 극대화 (초기 메타데이터 수신 속도업)
|
||||
sp.setBoolean(settings_pack.bool_types.enable_dht.swigValue(), true) // DHT 활성화
|
||||
// sp.setBoolean(settings_pack.bool_types.enable_pex.swigValue(), true) // Peer Exchange (피어 교환)
|
||||
sp.setBoolean(settings_pack.bool_types.enable_lsd.swigValue(), true) // 로컬 네트워크 탐색
|
||||
|
||||
// [3] 모바일 맞춤형 연결 최적화
|
||||
// 안드로이드는 데스크탑처럼 수천 개를 뚫으면 공유기가 뻗거나 앱 OOM이 발생할 수 있습니다.
|
||||
sp.connectionsLimit(400) // 글로벌 최대 연결 수 (기본값 보통 200)
|
||||
sp.activeDownloads(3) // 동시에 속도를 끌어올릴 다운로드 개수 (너무 많으면 속도가 분산됨)
|
||||
sp.activeLimit(5) // 활성 상태(업/다운 포함) 유지 최대 개수
|
||||
|
||||
// 세팅을 세션에 반영
|
||||
session.applySettings(sp)
|
||||
|
||||
session.addListener(object : AlertListener {
|
||||
override fun types(): IntArray? = intArrayOf(
|
||||
@ -211,15 +254,20 @@ class TorrentService : Service() {
|
||||
println("TorrentService: 마그넷 파싱 에러 - ${error.message()} / URI: $magnetUri")
|
||||
return
|
||||
}
|
||||
swigParams.max_connections = 150
|
||||
// 다운로드/업로드 제한 해제 (기본값이 -1이긴 하나 명시적으로 박아줌)
|
||||
swigParams.download_limit = -1
|
||||
swigParams.upload_limit = -1
|
||||
|
||||
// 참고: 만약 "순차 다운로드(동영상 스트리밍용)"가 필요하다면 아래 주석을 푸세요.
|
||||
// 단, 전체 다운로드 완료 속도는 일반 다운로드보다 느려집니다.
|
||||
// swigParams.flags = swigParams.flags.or_(libtorrent.getSequential_download())
|
||||
|
||||
// 2. 파싱 성공 시, 다운로드 경로 설정
|
||||
swigParams.setSave_path(tempDir.absolutePath)
|
||||
|
||||
// 3. 원시 세션에 비동기로 토렌트 추가
|
||||
session.swig().async_add_torrent(swigParams)
|
||||
|
||||
println("TorrentService: 마그넷 추가 성공!")
|
||||
|
||||
toast("마그넷 추가 성공 ${session.torrentHandles.size}개 진행중")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@ -289,15 +337,21 @@ class TorrentService : Service() {
|
||||
return
|
||||
}
|
||||
|
||||
val appFolderName = "Lunatic"
|
||||
val baseDownloadPath = "${Environment.DIRECTORY_DOWNLOADS}/$appFolderName"
|
||||
var root = File(baseDownloadPath)
|
||||
if (root.exists() == false) {
|
||||
root.mkdirs()
|
||||
}
|
||||
// 1. 단일 파일인 경우
|
||||
if (sourcePath.isFile) {
|
||||
copySingleFileToDownloads(sourcePath, Environment.DIRECTORY_DOWNLOADS)
|
||||
copySingleFileToDownloads(sourcePath, baseDownloadPath)
|
||||
}
|
||||
// 2. 다중 파일(폴더)인 경우
|
||||
else if (sourcePath.isDirectory) {
|
||||
sourcePath.walkTopDown().filter { it.isFile }.forEach { file ->
|
||||
val relativeSubPath = file.parentFile?.absolutePath?.substringAfter(sourcePath.absolutePath) ?: ""
|
||||
val destRelativePath = "${Environment.DIRECTORY_DOWNLOADS}/$torrentName$relativeSubPath"
|
||||
val destRelativePath = "$baseDownloadPath/$torrentName$relativeSubPath"
|
||||
|
||||
copySingleFileToDownloads(file, destRelativePath)
|
||||
}
|
||||
@ -391,17 +445,46 @@ class TorrentService : Service() {
|
||||
|
||||
override fun onBind(intent: Intent): IBinder = binder
|
||||
|
||||
private fun updateNotification(tasks: List<TorrentTask>) {
|
||||
if (tasks.isEmpty()) return
|
||||
|
||||
// 다운로드 중인 첫 번째 항목 찾기
|
||||
val activeTask = tasks.firstOrNull { !it.isPaused && it.progress < 100f } ?: tasks.first()
|
||||
|
||||
// 여러 개를 다운 중일 경우 텍스트 처리
|
||||
val title = if (tasks.size > 1) {
|
||||
"${activeTask.name} 외 ${tasks.size - 1}건"
|
||||
} else {
|
||||
activeTask.name
|
||||
}
|
||||
|
||||
notificationBuilder.setContentTitle(title)
|
||||
notificationBuilder.setContentText(String.format("다운로드 중... %.1f%%", activeTask.progress))
|
||||
notificationBuilder.setProgress(100, activeTask.progress.toInt(), false) // 프로그레스바 세팅
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
private fun startForegroundService() {
|
||||
val channelId = "torrent_channel"
|
||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(channelId, "Torrent Service", NotificationManager.IMPORTANCE_LOW)
|
||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
manager.createNotificationChannel(channel)
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
"Torrent Service",
|
||||
// 💡 중요: 진행률이 1초마다 갱신되므로 소리가 나지 않게 LOW로 설정합니다.
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
val notification = NotificationCompat.Builder(this, channelId)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentTitle("Torrent Manager Active")
|
||||
.build()
|
||||
startForeground(101, notification)
|
||||
|
||||
notificationBuilder = NotificationCompat.Builder(this, channelId)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download) // 다운로드 아이콘
|
||||
.setContentTitle("토렌트 엔진 준비 중...")
|
||||
.setOnlyAlertOnce(true) // 💡 갱신될 때마다 알림음/진동이 울리지 않게 설정
|
||||
.setOngoing(true)
|
||||
|
||||
startForeground(NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user