This commit is contained in:
lunaticbum 2026-04-05 09:04:46 +09:00
parent 8c3bc96417
commit ba869e8eb4
4 changed files with 121 additions and 96 deletions

View File

@ -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()

View File

@ -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 = "" // 💡 실시간 검색어 저장용 변수

View File

@ -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))
}
}
}

View File

@ -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)
}