...
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()
|
val realm = WorkersDb.getRealm()
|
||||||
|
|
||||||
// 5회 반복 호출
|
// 5회 반복 호출
|
||||||
for (i in 1..5) {
|
|
||||||
try {
|
try {
|
||||||
// 1. ZenQuotes (영어 명언) 호출 - 배열 형태로 응답옴
|
|
||||||
val zenRequest = Request.Builder().url("https://zenquotes.io/api/random").build()
|
val zenRequest = Request.Builder().url("https://zenquotes.io/api/random").build()
|
||||||
val zenResponse = client.newCall(zenRequest).execute()
|
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 korRequest = Request.Builder().url("https://korean-advice-open-api.vercel.app/api/advice").build()
|
||||||
val korResponse = client.newCall(korRequest).execute()
|
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) {
|
} 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>(
|
val wallpaperRequest = PeriodicWorkRequestBuilder<WallContentsWorker>(
|
||||||
120, java.util.concurrent.TimeUnit.MINUTES // 💡 2시간 간격으로 설정
|
2, TimeUnit.HOURS // 1시간 간격 설정
|
||||||
)
|
)
|
||||||
.setConstraints(
|
.setConstraints(
|
||||||
Constraints.Builder()
|
Constraints.Builder()
|
||||||
@ -735,7 +731,7 @@ class ForeGroundService : Service() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val quoteRequest = PeriodicWorkRequestBuilder<QuoteFetchWorker>(
|
val quoteRequest = PeriodicWorkRequestBuilder<QuoteFetchWorker>(
|
||||||
1, TimeUnit.HOURS // 1시간 간격 설정
|
2, TimeUnit.HOURS // 1시간 간격 설정
|
||||||
)
|
)
|
||||||
.setConstraints(
|
.setConstraints(
|
||||||
Constraints.Builder()
|
Constraints.Builder()
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
enum class FileFilterType(val label : String) { ALL("전체"), IMAGE("이미지"), VIDEO("영상"), DOCUMENT("문서"), OTHER("기타") }
|
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 }
|
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 allFiles = listOf<File>()
|
||||||
|
|
||||||
private var currentFilter = FileFilterType.ALL
|
private var currentFilter = FileFilterType.ALL
|
||||||
private var currentSort = FileSortType.NAME
|
private var currentSort = FileSortType.LAST_USED
|
||||||
private var currentViewMode = FileViewMode.LIST_TEXT
|
private var currentViewMode = FileViewMode.LIST_TEXT
|
||||||
private var isDescending = true
|
private var isDescending = true
|
||||||
private var searchQuery = "" // 💡 실시간 검색어 저장용 변수
|
private var searchQuery = "" // 💡 실시간 검색어 저장용 변수
|
||||||
|
|||||||
@ -308,41 +308,64 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
|
|
||||||
val requiredSizeRatio = 0.5
|
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() {
|
fun loadFiles() {
|
||||||
if (!mediaDir.exists()) mediaDir.mkdirs()
|
if (!mediaDir.exists()) mediaDir.mkdirs()
|
||||||
|
|
||||||
Log.d(TAG, "Found ${mediaFiles.size} media files.")
|
val allFiles = mediaDir.listFiles()?.filter { supportedExtensions.contains(it.extension.lowercase()) } ?: return
|
||||||
|
|
||||||
val allFiles = mediaDir.listFiles().filter { supportedExtensions.contains(it.extension) }
|
|
||||||
val trashFolder = File(mediaDir, "low_res_backup")
|
val trashFolder = File(mediaDir, "low_res_backup")
|
||||||
if (!trashFolder.exists()) trashFolder.mkdirs()
|
if (!trashFolder.exists()) trashFolder.mkdirs()
|
||||||
val invalidImages = mutableListOf<File>()
|
|
||||||
val wm = WallpaperManager.getInstance(this@MyWallpaperService)
|
val wm = WallpaperManager.getInstance(this@MyWallpaperService)
|
||||||
val minWidth = wm.desiredMinimumWidth
|
val requiredSize = Math.max(wm.desiredMinimumWidth, wm.desiredMinimumHeight) * requiredSizeRatio
|
||||||
val minHeight = wm.desiredMinimumHeight
|
|
||||||
val requiredSize = Math.max(minWidth, minHeight).times(requiredSizeRatio)
|
val videoExtensions = listOf("mp4", "mkv", "avi", "mov", "webm")
|
||||||
|
val imageExtensions = listOf("jpg", "jpeg", "png", "bmp", "webp")
|
||||||
|
|
||||||
for (file in allFiles) {
|
for (file in allFiles) {
|
||||||
|
val ext = file.extension.lowercase()
|
||||||
if (file.isFile && (file.extension.equals("jpg", true) ||
|
var width = 0
|
||||||
file.extension.equals("png", true) ||
|
var height = 0
|
||||||
file.extension.equals("jpeg", true) ||
|
var isOk = false
|
||||||
file.extension.equals("bmp", true) || // BMP 추가
|
when {
|
||||||
file.extension.equals("webp", true))) {
|
// 이미지 처리
|
||||||
|
imageExtensions.contains(ext) -> {
|
||||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
BitmapFactory.decodeFile(file.absolutePath, options)
|
BitmapFactory.decodeFile(file.absolutePath, options)
|
||||||
if (options.outWidth >= requiredSize && options.outHeight >= requiredSize) {
|
width = options.outWidth
|
||||||
mediaFiles.add(file)
|
height = options.outHeight
|
||||||
} else {
|
// Blog.LOGE("loadFiles imageExtensions width $width height $height")
|
||||||
invalidImages.add(file) // 조건 미달
|
}
|
||||||
|
// 동영상 처리
|
||||||
|
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 ->
|
if (isOk || (width >= requiredSize && height >= requiredSize)) {
|
||||||
val targetFile = File(trashFolder, file.name)
|
if (!mediaFiles.contains(file)) mediaFiles.add(file)
|
||||||
file.renameTo(targetFile) // 파일 이동
|
} 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() {
|
private val batteryReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
isCharging = when (intent?.action) {
|
// isCharging = when (intent?.action) {
|
||||||
Intent.ACTION_POWER_CONNECTED -> true
|
// Intent.ACTION_POWER_CONNECTED -> true
|
||||||
Intent.ACTION_POWER_DISCONNECTED -> false
|
// Intent.ACTION_POWER_DISCONNECTED -> false
|
||||||
else -> isCharging
|
// else -> isCharging
|
||||||
}
|
// }
|
||||||
|
|
||||||
val intent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
val intent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ class TorrentService : Service() {
|
|||||||
|
|
||||||
val batteryPct = (level / scale.toFloat() * 100).toInt()
|
val batteryPct = (level / scale.toFloat() * 100).toInt()
|
||||||
isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||||
status == BatteryManager.BATTERY_STATUS_FULL
|
status == BatteryManager.BATTERY_STATUS_FULL || (batteryPct > 95)
|
||||||
|
|
||||||
var speedText = ""
|
var speedText = ""
|
||||||
var detailsText = ""
|
var detailsText = ""
|
||||||
@ -158,76 +158,82 @@ class TorrentService : Service() {
|
|||||||
|
|
||||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||||
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
||||||
val isWifi = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
isWifiConnected = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||||
isWifiConnected = isWifi
|
|
||||||
updateSessionState()
|
updateSessionState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connectivityManager.registerNetworkCallback(request, networkCallback!!)
|
connectivityManager.registerNetworkCallback(request, networkCallback!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var lastUpdateTime = -1L
|
||||||
/**
|
/**
|
||||||
* 핵심 제어 로직: 충전 중일 때만 세션을 열고, Wi-Fi 여부에 따라 슬롯 조절
|
* 핵심 제어 로직: 충전 중일 때만 세션을 열고, Wi-Fi 여부에 따라 슬롯 조절
|
||||||
*/
|
*/
|
||||||
private fun updateSessionState() {
|
private fun updateSessionState() {
|
||||||
if (session.isPaused) session.resume()
|
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 LOW_SPEED_THRESHOLD = 10 * 1024 // 10 KB/s 미만을 '속도 저조'로 판단
|
||||||
val vector = session.swig().get_torrents()
|
val PENALTY_SCORE = 1000 // 속도가 낮을 경우 부여할 페널티 (대기열 순서보다 큰 값)
|
||||||
// (핸들, 계산된 우선순위 점수)
|
|
||||||
val torrentsWithMetadata = mutableListOf<kotlin.Pair<TorrentHandle, Int>>()
|
|
||||||
val torrentsWithoutMetadata = mutableListOf<TorrentHandle>()
|
|
||||||
|
|
||||||
val LOW_SPEED_THRESHOLD = 10 * 1024 // 10 KB/s 미만을 '속도 저조'로 판단
|
for (i in 0 until vector.size.toInt()) {
|
||||||
val PENALTY_SCORE = 1000 // 속도가 낮을 경우 부여할 페널티 (대기열 순서보다 큰 값)
|
val handle = TorrentHandle(vector.get(i))
|
||||||
|
if (!handle.isValid) continue
|
||||||
|
|
||||||
for (i in 0 until vector.size.toInt()) {
|
val status = handle.status()
|
||||||
val handle = TorrentHandle(vector.get(i))
|
if (!status.hasMetadata()) {
|
||||||
if (!handle.isValid) continue
|
torrentsWithoutMetadata.add(handle)
|
||||||
|
} else if (!status.isFinished) {
|
||||||
|
val currentRate = status.downloadPayloadRate()
|
||||||
|
val basePriority = status.queuePosition()
|
||||||
|
|
||||||
val status = handle.status()
|
// 속도가 너무 낮거나 0인 경우 페널티 부여
|
||||||
if (!status.hasMetadata()) {
|
// 단, 막 시작해서 속도가 측정되지 않은 경우를 위해 추가 로직을 넣을 수 있지만
|
||||||
torrentsWithoutMetadata.add(handle)
|
// 주기적 업데이트(2초)가 되므로 페널티를 받아도 다음 턴에 기회를 잡을 수 있습니다.
|
||||||
} else if (!status.isFinished) {
|
val finalScore = if (currentRate < LOW_SPEED_THRESHOLD) {
|
||||||
val currentRate = status.downloadPayloadRate()
|
basePriority + PENALTY_SCORE
|
||||||
val basePriority = status.queuePosition()
|
} else {
|
||||||
|
basePriority
|
||||||
|
}
|
||||||
|
|
||||||
// 속도가 너무 낮거나 0인 경우 페널티 부여
|
torrentsWithMetadata.add(handle to finalScore)
|
||||||
// 단, 막 시작해서 속도가 측정되지 않은 경우를 위해 추가 로직을 넣을 수 있지만
|
|
||||||
// 주기적 업데이트(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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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"
|
private val TRACKER_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
|
||||||
@ -329,12 +335,12 @@ class TorrentService : Service() {
|
|||||||
while (isActive) {
|
while (isActive) {
|
||||||
updateSessionState()
|
updateSessionState()
|
||||||
// 충전 중이고 세션이 돌아갈 때만 C++ 엔진에 상태 업데이트 요청
|
// 충전 중이고 세션이 돌아갈 때만 C++ 엔진에 상태 업데이트 요청
|
||||||
var delayTime = 2000L
|
var delayTime = 30000L
|
||||||
if (isCharging) {
|
if (isCharging) {
|
||||||
session.postTorrentUpdates()
|
session.postTorrentUpdates()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
delayTime = 10000L
|
delayTime = 60000L
|
||||||
}
|
}
|
||||||
delay(delayTime)
|
delay(delayTime)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user