...
This commit is contained in:
parent
8c3bc96417
commit
ba869e8eb4
@ -104,9 +104,9 @@ class QuoteFetchWorker(private val context: Context, params: WorkerParameters) :
|
||||
val realm = WorkersDb.getRealm()
|
||||
|
||||
// 5회 반복 호출
|
||||
for (i in 1..5) {
|
||||
|
||||
try {
|
||||
// 1. ZenQuotes (영어 명언) 호출 - 배열 형태로 응답옴
|
||||
|
||||
val zenRequest = Request.Builder().url("https://zenquotes.io/api/random").build()
|
||||
val zenResponse = client.newCall(zenRequest).execute()
|
||||
|
||||
@ -129,7 +129,7 @@ class QuoteFetchWorker(private val context: Context, params: WorkerParameters) :
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Korean Advice (한국어 명언) 호출 - 객체 형태로 응답옴
|
||||
|
||||
val korRequest = Request.Builder().url("https://korean-advice-open-api.vercel.app/api/advice").build()
|
||||
val korResponse = client.newCall(korRequest).execute()
|
||||
|
||||
@ -147,14 +147,10 @@ class QuoteFetchWorker(private val context: Context, params: WorkerParameters) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 무료 API Rate Limit(호출 제한) 방어를 위해 1.5초 대기
|
||||
delay(1500)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Blog.LOGE("Quote Fetch Iteration $i Failed", e)
|
||||
Blog.LOGE("Quote Failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -719,7 +715,7 @@ class ForeGroundService : Service() {
|
||||
|
||||
|
||||
val wallpaperRequest = PeriodicWorkRequestBuilder<WallContentsWorker>(
|
||||
120, java.util.concurrent.TimeUnit.MINUTES // 💡 2시간 간격으로 설정
|
||||
2, TimeUnit.HOURS // 1시간 간격 설정
|
||||
)
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
@ -735,7 +731,7 @@ class ForeGroundService : Service() {
|
||||
)
|
||||
|
||||
val quoteRequest = PeriodicWorkRequestBuilder<QuoteFetchWorker>(
|
||||
1, TimeUnit.HOURS // 1시간 간격 설정
|
||||
2, TimeUnit.HOURS // 1시간 간격 설정
|
||||
)
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
|
||||
@ -36,7 +36,7 @@ import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
enum class FileFilterType(val label : String) { ALL("전체"), IMAGE("이미지"), VIDEO("영상"), DOCUMENT("문서"), OTHER("기타") }
|
||||
enum class FileSortType(val label : String) { NAME("파일명") , DOWNLOAD_DATE("다운로드"), LAST_USED("최근 사용"), FREQUENTLY_USED("자주 사용"), SIZE("용량") }
|
||||
enum class FileSortType(val label : String) { LAST_USED("최근 사용"), FREQUENTLY_USED("자주 사용"),NAME("파일명") , DOWNLOAD_DATE("다운로드"), SIZE("용량") }
|
||||
enum class FileViewMode { LIST_TEXT, LIST_THUMB, GRID_LARGE, GRID_SMALL }
|
||||
|
||||
// 💡 숫자 크기를 인식하는 정렬 비교자
|
||||
@ -87,7 +87,7 @@ class CompletedFilesFragment : Fragment() {
|
||||
private var allFiles = listOf<File>()
|
||||
|
||||
private var currentFilter = FileFilterType.ALL
|
||||
private var currentSort = FileSortType.NAME
|
||||
private var currentSort = FileSortType.LAST_USED
|
||||
private var currentViewMode = FileViewMode.LIST_TEXT
|
||||
private var isDescending = true
|
||||
private var searchQuery = "" // 💡 실시간 검색어 저장용 변수
|
||||
|
||||
@ -308,41 +308,64 @@ class MyWallpaperService : WallpaperService() {
|
||||
|
||||
val requiredSizeRatio = 0.5
|
||||
|
||||
private fun getVideoSize(file: File): Pair<Int, Int>? {
|
||||
val retriever = android.media.MediaMetadataRetriever()
|
||||
return try {
|
||||
retriever.setDataSource(file.absolutePath)
|
||||
val width = retriever.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() ?: 0
|
||||
val height = retriever.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() ?: 0
|
||||
Pair(width, height)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
} finally {
|
||||
retriever.release()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadFiles() {
|
||||
if (!mediaDir.exists()) mediaDir.mkdirs()
|
||||
|
||||
Log.d(TAG, "Found ${mediaFiles.size} media files.")
|
||||
|
||||
val allFiles = mediaDir.listFiles().filter { supportedExtensions.contains(it.extension) }
|
||||
val allFiles = mediaDir.listFiles()?.filter { supportedExtensions.contains(it.extension.lowercase()) } ?: return
|
||||
val trashFolder = File(mediaDir, "low_res_backup")
|
||||
if (!trashFolder.exists()) trashFolder.mkdirs()
|
||||
val invalidImages = mutableListOf<File>()
|
||||
|
||||
val wm = WallpaperManager.getInstance(this@MyWallpaperService)
|
||||
val minWidth = wm.desiredMinimumWidth
|
||||
val minHeight = wm.desiredMinimumHeight
|
||||
val requiredSize = Math.max(minWidth, minHeight).times(requiredSizeRatio)
|
||||
val requiredSize = Math.max(wm.desiredMinimumWidth, wm.desiredMinimumHeight) * requiredSizeRatio
|
||||
|
||||
val videoExtensions = listOf("mp4", "mkv", "avi", "mov", "webm")
|
||||
val imageExtensions = listOf("jpg", "jpeg", "png", "bmp", "webp")
|
||||
|
||||
for (file in allFiles) {
|
||||
|
||||
if (file.isFile && (file.extension.equals("jpg", true) ||
|
||||
file.extension.equals("png", true) ||
|
||||
file.extension.equals("jpeg", true) ||
|
||||
file.extension.equals("bmp", true) || // BMP 추가
|
||||
file.extension.equals("webp", true))) {
|
||||
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeFile(file.absolutePath, options)
|
||||
if (options.outWidth >= requiredSize && options.outHeight >= requiredSize) {
|
||||
mediaFiles.add(file)
|
||||
} else {
|
||||
invalidImages.add(file) // 조건 미달
|
||||
val ext = file.extension.lowercase()
|
||||
var width = 0
|
||||
var height = 0
|
||||
var isOk = false
|
||||
when {
|
||||
// 이미지 처리
|
||||
imageExtensions.contains(ext) -> {
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeFile(file.absolutePath, options)
|
||||
width = options.outWidth
|
||||
height = options.outHeight
|
||||
// Blog.LOGE("loadFiles imageExtensions width $width height $height")
|
||||
}
|
||||
// 동영상 처리
|
||||
videoExtensions.contains(ext) -> {
|
||||
val size = getVideoSize(file)
|
||||
width = size?.first ?: 0
|
||||
height = size?.second ?: 0
|
||||
isOk = (width >= requiredSize*0.65 && height >= requiredSize*0.65)
|
||||
Blog.LOGE("loadFiles videoExtensions requiredSize $requiredSize width $width height $height [$isOk]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 부적합 이미지 이동 처리 (Job 밖에서 따로 돌려도 무방)
|
||||
invalidImages.forEach { file ->
|
||||
val targetFile = File(trashFolder, file.name)
|
||||
file.renameTo(targetFile) // 파일 이동
|
||||
// 사이즈 검사 및 분류
|
||||
if (isOk || (width >= requiredSize && height >= requiredSize)) {
|
||||
if (!mediaFiles.contains(file)) mediaFiles.add(file)
|
||||
} else {
|
||||
// Log.w(TAG, "Low resolution file filtered: ${file.name} (${width}x${height})")
|
||||
file.renameTo(File(trashFolder, file.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -97,11 +97,11 @@ class TorrentService : Service() {
|
||||
|
||||
private val batteryReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
isCharging = when (intent?.action) {
|
||||
Intent.ACTION_POWER_CONNECTED -> true
|
||||
Intent.ACTION_POWER_DISCONNECTED -> false
|
||||
else -> isCharging
|
||||
}
|
||||
// isCharging = when (intent?.action) {
|
||||
// Intent.ACTION_POWER_CONNECTED -> true
|
||||
// Intent.ACTION_POWER_DISCONNECTED -> false
|
||||
// else -> isCharging
|
||||
// }
|
||||
|
||||
val intent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
|
||||
@ -112,7 +112,7 @@ class TorrentService : Service() {
|
||||
|
||||
val batteryPct = (level / scale.toFloat() * 100).toInt()
|
||||
isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
status == BatteryManager.BATTERY_STATUS_FULL
|
||||
status == BatteryManager.BATTERY_STATUS_FULL || (batteryPct > 95)
|
||||
|
||||
var speedText = ""
|
||||
var detailsText = ""
|
||||
@ -158,76 +158,82 @@ class TorrentService : Service() {
|
||||
|
||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
||||
val isWifi = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
isWifiConnected = isWifi
|
||||
isWifiConnected = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
updateSessionState()
|
||||
}
|
||||
}
|
||||
connectivityManager.registerNetworkCallback(request, networkCallback!!)
|
||||
}
|
||||
|
||||
|
||||
var lastUpdateTime = -1L
|
||||
/**
|
||||
* 핵심 제어 로직: 충전 중일 때만 세션을 열고, Wi-Fi 여부에 따라 슬롯 조절
|
||||
*/
|
||||
private fun updateSessionState() {
|
||||
if (session.isPaused) session.resume()
|
||||
var curentTime = System.currentTimeMillis()
|
||||
if (curentTime - lastUpdateTime > 5000) {
|
||||
serviceScope.launch {
|
||||
lastUpdateTime = curentTime
|
||||
Blog.LOGE("isWifiConnected $isWifiConnected isCharging $isCharging")
|
||||
val vector = session.swig().get_torrents()
|
||||
// (핸들, 계산된 우선순위 점수)
|
||||
val torrentsWithMetadata = mutableListOf<kotlin.Pair<TorrentHandle, Int>>()
|
||||
val torrentsWithoutMetadata = mutableListOf<TorrentHandle>()
|
||||
|
||||
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 // 속도가 낮을 경우 부여할 페널티 (대기열 순서보다 큰 값)
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 속도가 너무 낮거나 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) 8 else 1
|
||||
val sortedByPriority = torrentsWithMetadata.sortedBy { it.second }
|
||||
|
||||
sortedByPriority.forEachIndexed { index, pair ->
|
||||
val handle = pair.first
|
||||
if (index < maxSlots) {
|
||||
handle.swig().resume()
|
||||
} else {
|
||||
handle.pause()
|
||||
torrentsWithMetadata.add(handle to finalScore)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 배터리 모드
|
||||
torrentsWithMetadata.forEach { it.first.pause() }
|
||||
}
|
||||
|
||||
refreshTorrentStats()
|
||||
// 1. 메타데이터 미수신: 무조건 유지
|
||||
torrentsWithoutMetadata.forEach { it.swig().resume() }
|
||||
|
||||
// 2. 파일 다운로드: 계산된 점수(finalScore)가 낮은 순으로 정렬
|
||||
if (isCharging) {
|
||||
val maxSlots = if (isWifiConnected) 8 else 1
|
||||
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"
|
||||
@ -329,12 +335,12 @@ class TorrentService : Service() {
|
||||
while (isActive) {
|
||||
updateSessionState()
|
||||
// 충전 중이고 세션이 돌아갈 때만 C++ 엔진에 상태 업데이트 요청
|
||||
var delayTime = 2000L
|
||||
var delayTime = 30000L
|
||||
if (isCharging) {
|
||||
session.postTorrentUpdates()
|
||||
|
||||
} else {
|
||||
delayTime = 10000L
|
||||
delayTime = 60000L
|
||||
}
|
||||
delay(delayTime)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user