65 changed files with 876 additions and 231 deletions
@ -1,3 +0,0 @@ |
|||||||
package tv.anypoint.domain.agent.ad |
|
||||||
|
|
||||||
data class AssetConvertResponse(val id: Long) |
|
||||||
@ -1,3 +0,0 @@ |
|||||||
package tv.anypoint.domain.agent.ad |
|
||||||
|
|
||||||
data class VastResponse(val id: Long) |
|
||||||
@ -0,0 +1,13 @@ |
|||||||
|
package tv.anypoint.dsl |
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean |
||||||
|
import org.springframework.context.annotation.Configuration |
||||||
|
import org.springframework.context.annotation.Scope |
||||||
|
import tv.anypoint.dsl.model.TcContext |
||||||
|
|
||||||
|
@Configuration |
||||||
|
class TestCaseConfig { |
||||||
|
@Bean |
||||||
|
@Scope("singleton") |
||||||
|
fun tcContext(): TcContext = TcContext() |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
package tv.anypoint.dsl.model |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
@Serializable |
||||||
|
class Log( |
||||||
|
val logcatFilePath: String |
||||||
|
) { |
||||||
|
var startLine: Int = 0 |
||||||
|
var cursor: Int = 0 |
||||||
|
var lastLine: Int = 0 |
||||||
|
} |
||||||
@ -1,8 +0,0 @@ |
|||||||
package tv.anypoint.dsl.model |
|
||||||
|
|
||||||
class LogInfo( |
|
||||||
val absoluteFilePath: String |
|
||||||
) { |
|
||||||
var cursor: Long = 0 |
|
||||||
var lastLine: Long = 0 |
|
||||||
} |
|
||||||
@ -0,0 +1,5 @@ |
|||||||
|
package tv.anypoint.dsl.model |
||||||
|
|
||||||
|
class TcContext { |
||||||
|
var tc: Tc = Tc() |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
package tv.anypoint.dsl.model |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
@Serializable |
||||||
|
data class TcFailEvent( |
||||||
|
val tcName: String, |
||||||
|
val errorMessage: String |
||||||
|
) |
||||||
@ -1,11 +0,0 @@ |
|||||||
package tv.anypoint.dsl.service |
|
||||||
|
|
||||||
import org.springframework.stereotype.Service |
|
||||||
import tv.anypoint.dsl.Adb |
|
||||||
|
|
||||||
@Service |
|
||||||
class AdbTransmitter { |
|
||||||
fun transmit(req: Adb) { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,41 @@ |
|||||||
|
package tv.anypoint.proxy.adapter |
||||||
|
|
||||||
|
import kotlinx.serialization.json.Json |
||||||
|
import okhttp3.MediaType.Companion.toMediaType |
||||||
|
import org.springframework.context.annotation.Bean |
||||||
|
import org.springframework.context.annotation.Configuration |
||||||
|
import retrofit2.Call |
||||||
|
import retrofit2.Retrofit |
||||||
|
import retrofit2.converter.kotlinx.serialization.asConverterFactory |
||||||
|
import retrofit2.http.Body |
||||||
|
import retrofit2.http.POST |
||||||
|
import retrofit2.http.Query |
||||||
|
import tv.anypoint.ApplicationProperties |
||||||
|
import tv.anypoint.proxy.model.agent.ad.AdsResponse |
||||||
|
import tv.anypoint.proxy.model.agent.ad.AdsSyncRequest |
||||||
|
|
||||||
|
interface AssignAdapter { |
||||||
|
@POST("/v3/device/ads") |
||||||
|
fun ads( |
||||||
|
@Query("deviceId") deviceId: Long, |
||||||
|
@Query("freeStorage") freeStorage: Long = 0, |
||||||
|
@Query("usedStorage") usedStorage: Long = 0 |
||||||
|
): Call<AdsResponse> |
||||||
|
|
||||||
|
@POST("/v3/device/sync/ads") |
||||||
|
fun syncAds( |
||||||
|
@Body request: AdsSyncRequest |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration |
||||||
|
class AssignAdapterConfiguration { |
||||||
|
@Bean |
||||||
|
fun assignAdapter(json: Json, applicationProperties: ApplicationProperties): AssignAdapter { |
||||||
|
val retrofit = Retrofit.Builder() |
||||||
|
.baseUrl(applicationProperties.endpoints.assign) |
||||||
|
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) |
||||||
|
.build() |
||||||
|
return retrofit.create(AssignAdapter::class.java) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
package tv.anypoint.proxy.adapter |
||||||
|
|
||||||
|
import kotlinx.serialization.json.Json |
||||||
|
import okhttp3.MediaType.Companion.toMediaType |
||||||
|
import org.springframework.context.annotation.Bean |
||||||
|
import org.springframework.context.annotation.Configuration |
||||||
|
import retrofit2.Call |
||||||
|
import retrofit2.Retrofit |
||||||
|
import retrofit2.converter.kotlinx.serialization.asConverterFactory |
||||||
|
import retrofit2.http.Body |
||||||
|
import retrofit2.http.POST |
||||||
|
import tv.anypoint.ApplicationProperties |
||||||
|
import tv.anypoint.proxy.model.agent.AuthRequest |
||||||
|
import tv.anypoint.proxy.model.agent.AuthResponse |
||||||
|
|
||||||
|
|
||||||
|
interface AuthAdapter { |
||||||
|
@POST("/v3/device/auth") |
||||||
|
fun auth( |
||||||
|
@Body request: AuthRequest |
||||||
|
): Call<AuthResponse> |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration |
||||||
|
class AuthAdapterConfiguration { |
||||||
|
@Bean |
||||||
|
fun authAdapter(json: Json, applicationProperties: ApplicationProperties): AuthAdapter { |
||||||
|
val retrofit = Retrofit.Builder() |
||||||
|
.baseUrl(applicationProperties.endpoints.auth) |
||||||
|
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) |
||||||
|
.build() |
||||||
|
return retrofit.create(AuthAdapter::class.java) |
||||||
|
} |
||||||
|
} |
||||||
@ -1,30 +0,0 @@ |
|||||||
package tv.anypoint.proxy.adapter |
|
||||||
|
|
||||||
import org.springframework.cloud.openfeign.FeignClient |
|
||||||
import org.springframework.web.bind.annotation.PostMapping |
|
||||||
import org.springframework.web.bind.annotation.RequestBody |
|
||||||
import org.springframework.web.bind.annotation.RequestParam |
|
||||||
import tv.anypoint.domain.agent.AuthRequest |
|
||||||
import tv.anypoint.domain.agent.AuthResponse |
|
||||||
import tv.anypoint.domain.agent.ad.AdsResponse |
|
||||||
import tv.anypoint.domain.agent.ad.AdsSyncRequest |
|
||||||
|
|
||||||
|
|
||||||
@FeignClient( |
|
||||||
name = "DeviceV3Adapter", |
|
||||||
path = "/v3/device" |
|
||||||
) |
|
||||||
interface DeviceV3Adapter { |
|
||||||
@PostMapping("/auth") |
|
||||||
fun auth(@RequestBody body: AuthRequest): AuthResponse |
|
||||||
|
|
||||||
@PostMapping("/ads") |
|
||||||
fun ads( |
|
||||||
@RequestParam(required = true) deviceId: Long, |
|
||||||
@RequestParam("freeStorage", required = true, defaultValue = "0") freeStorage: Long = 0, |
|
||||||
@RequestParam("usedStorage", required = true, defaultValue = "0") usedStorage: Long = 0 |
|
||||||
): AdsResponse |
|
||||||
|
|
||||||
@PostMapping("/sync/ads") |
|
||||||
fun syncAds(@RequestBody request: AdsSyncRequest) |
|
||||||
} |
|
||||||
@ -0,0 +1,32 @@ |
|||||||
|
package tv.anypoint.proxy.config |
||||||
|
|
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import kotlinx.coroutines.runBlocking |
||||||
|
import mu.KLogging |
||||||
|
import org.springframework.context.annotation.Bean |
||||||
|
import org.springframework.context.annotation.Configuration |
||||||
|
import se.vidstige.jadb.JadbDevice |
||||||
|
import tv.anypoint.ApplicationProperties |
||||||
|
import tv.anypoint.proxy.util.JadbDeviceSerialUtil |
||||||
|
import tv.anypoint.proxy.util.NetworkUtil |
||||||
|
import tv.anypoint.proxy.util.getDevice |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
@Configuration |
||||||
|
class ProxyApplicationConfig( |
||||||
|
private val applicationProperties: ApplicationProperties |
||||||
|
) { |
||||||
|
@Bean |
||||||
|
fun jadbDevice(): JadbDevice = getDevice( |
||||||
|
stbIp = applicationProperties.stb.ip, |
||||||
|
stbPort = applicationProperties.stb.port |
||||||
|
) |
||||||
|
|
||||||
|
@Bean |
||||||
|
fun serverIp(jadbDevice: JadbDevice): String = NetworkUtil.getPrivateIpV4( |
||||||
|
JadbDeviceSerialUtil.serialToAddress(jadbDevice.serial) |
||||||
|
?: throw RuntimeException("Can't get server ip address") |
||||||
|
) |
||||||
|
|
||||||
|
companion object : KLogging() |
||||||
|
} |
||||||
@ -1,4 +1,4 @@ |
|||||||
package tv.anypoint.domain.adb |
package tv.anypoint.proxy.model.adb |
||||||
|
|
||||||
enum class ChangeCommand { |
enum class ChangeCommand { |
||||||
CHANGE_API_ENDPOINT, |
CHANGE_API_ENDPOINT, |
||||||
@ -1,8 +1,8 @@ |
|||||||
package tv.anypoint.domain.adb |
package tv.anypoint.proxy.model.adb |
||||||
|
|
||||||
enum class ExtraKey(val string: String) { |
enum class ExtraKey(val string: String) { |
||||||
EXTRA_JSON("EXTRA_JSON"), |
EXTRA_JSON("EXTRA_JSON"), |
||||||
CHANGE_COMMAND("change.command"), |
CHANGE_COMMAND("change.command"), |
||||||
API_ENDPOINT("api.endpoint"), |
API_ENDPOINT("api.endpoint"), |
||||||
TEST_DEVICE("test.device") |
TEST_DEVICE("test.device") |
||||||
} |
} |
||||||
@ -1,5 +1,5 @@ |
|||||||
package tv.anypoint.domain.adb |
package tv.anypoint.proxy.model.adb |
||||||
|
|
||||||
enum class IntentAction(val string: String) { |
enum class IntentAction(val string: String) { |
||||||
CHANGE_TEST_PROPERTY("tv.anypoint.agent.app.CHANGE_TEST_PROPERTY") |
CHANGE_TEST_PROPERTY("tv.anypoint.agent.app.CHANGE_TEST_PROPERTY") |
||||||
} |
} |
||||||
@ -1,5 +1,8 @@ |
|||||||
package tv.anypoint.domain.agent |
package tv.anypoint.proxy.model.agent |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
@Serializable |
||||||
data class AuthRequest( |
data class AuthRequest( |
||||||
var fingerPrint: String = "", |
var fingerPrint: String = "", |
||||||
val soId: Int = 0, |
val soId: Int = 0, |
||||||
@ -1,4 +1,4 @@ |
|||||||
package tv.anypoint.domain.agent |
package tv.anypoint.proxy.model.agent |
||||||
|
|
||||||
data class Cue( |
data class Cue( |
||||||
var id: Long = 0, |
var id: Long = 0, |
||||||
@ -1,4 +1,4 @@ |
|||||||
package tv.anypoint.domain.agent |
package tv.anypoint.proxy.model.agent |
||||||
|
|
||||||
enum class CueOwner { |
enum class CueOwner { |
||||||
PP, |
PP, |
||||||
@ -1,7 +1,7 @@ |
|||||||
package tv.anypoint.domain.agent |
package tv.anypoint.proxy.model.agent |
||||||
|
|
||||||
enum class PlayType { |
enum class PlayType { |
||||||
DNP, |
DNP, |
||||||
LAZY_DNP, |
LAZY_DNP, |
||||||
STREAMING |
STREAMING |
||||||
} |
} |
||||||
@ -1,6 +1,9 @@ |
|||||||
package tv.anypoint.domain.agent |
package tv.anypoint.proxy.model.agent |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
@Serializable |
||||||
data class PlayerStateCheckParam( |
data class PlayerStateCheckParam( |
||||||
val playerStateCheckDuration: Int = 1500, |
val playerStateCheckDuration: Int = 1500, |
||||||
val maxPlayerInvalidateInterval: Int = 300 |
val maxPlayerInvalidateInterval: Int = 300 |
||||||
) |
) |
||||||
@ -1,5 +1,8 @@ |
|||||||
package tv.anypoint.domain.agent |
package tv.anypoint.proxy.model.agent |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
@Serializable |
||||||
data class ProgramProviderChannel( |
data class ProgramProviderChannel( |
||||||
val id: Int, |
val id: Int, |
||||||
val delay: Int = 0, |
val delay: Int = 0, |
||||||
@ -1,4 +1,4 @@ |
|||||||
package tv.anypoint.domain.agent |
package tv.anypoint.proxy.model.agent |
||||||
|
|
||||||
data class StateChangeLog( |
data class StateChangeLog( |
||||||
val soId: Int = 0, |
val soId: Int = 0, |
||||||
@ -1,5 +1,8 @@ |
|||||||
package tv.anypoint.domain.agent.ad |
package tv.anypoint.proxy.model.agent.ad |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
@Serializable |
||||||
data class AdsResponse( |
data class AdsResponse( |
||||||
val status: AdListResponseStatus = AdListResponseStatus.OK, |
val status: AdListResponseStatus = AdListResponseStatus.OK, |
||||||
val ads: List<Ad> = listOf(), |
val ads: List<Ad> = listOf(), |
||||||
@ -1,4 +1,4 @@ |
|||||||
package tv.anypoint.domain.agent.ad |
package tv.anypoint.proxy.model.agent.ad |
||||||
|
|
||||||
data class AdsSyncRequest( |
data class AdsSyncRequest( |
||||||
val deviceId: Long, |
val deviceId: Long, |
||||||
@ -1,7 +1,9 @@ |
|||||||
package tv.anypoint.domain.agent.ad |
package tv.anypoint.proxy.model.agent.ad |
||||||
|
|
||||||
import tv.anypoint.domain.agent.PlayType |
import kotlinx.serialization.Serializable |
||||||
|
import tv.anypoint.proxy.model.agent.PlayType |
||||||
|
|
||||||
|
@Serializable |
||||||
data class Asset( |
data class Asset( |
||||||
var assetId: Long = 0L, |
var assetId: Long = 0L, |
||||||
var crc: String = "0", |
var crc: String = "0", |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
package tv.anypoint.proxy.model.agent.ad |
||||||
|
|
||||||
|
data class AssetConvertResponse(val id: Long) |
||||||
@ -1,5 +1,8 @@ |
|||||||
package tv.anypoint.domain.agent.ad |
package tv.anypoint.proxy.model.agent.ad |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
@Serializable |
||||||
data class Click( |
data class Click( |
||||||
val targetType: String, |
val targetType: String, |
||||||
val targetValue: String, |
val targetValue: String, |
||||||
@ -1,6 +1,9 @@ |
|||||||
package tv.anypoint.domain.agent.ad |
package tv.anypoint.proxy.model.agent.ad |
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable |
||||||
|
|
||||||
|
|
||||||
|
@Serializable |
||||||
data class TargetAd( |
data class TargetAd( |
||||||
val hours: String? = null, |
val hours: String? = null, |
||||||
val days: String? = null, |
val days: String? = null, |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
package tv.anypoint.proxy.model.agent.ad |
||||||
|
|
||||||
|
data class VastResponse(val id: Long) |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
package tv.anypoint.proxy.model.push |
||||||
|
|
||||||
|
enum class PushCommandType(val string: String) { |
||||||
|
UPDATE_ASSET_COMMAND("UpdateAssetCommand"), |
||||||
|
UPDATE_SYSTEM_INFO("UpdateSystemInfo") |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
package tv.anypoint.proxy.service |
||||||
|
|
||||||
|
import kotlinx.coroutines.GlobalScope |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import mu.KLogging |
||||||
|
import org.springframework.stereotype.Service |
||||||
|
import tv.anypoint.ApplicationProperties |
||||||
|
import java.io.File |
||||||
|
import java.util.concurrent.TimeUnit |
||||||
|
|
||||||
|
@Service |
||||||
|
class LogcatService( |
||||||
|
private val applicationProperties: ApplicationProperties |
||||||
|
) { |
||||||
|
private var dumpProcess: Process? = null |
||||||
|
|
||||||
|
fun startDump() { |
||||||
|
val filePath = "${applicationProperties.fileRoot}/all.log" |
||||||
|
GlobalScope.launch { |
||||||
|
logger.info("start to save logcat. path: $filePath") |
||||||
|
launch { |
||||||
|
File(filePath).delete() |
||||||
|
ProcessBuilder("/bin/sh", "-c", "adb logcat -c").start() |
||||||
|
val processBuilder = ProcessBuilder("/bin/sh", "-c", "adb logcat -s AnypointAD") |
||||||
|
|
||||||
|
processBuilder.redirectOutput(File(filePath)) |
||||||
|
val process = processBuilder.start() |
||||||
|
process.onExit().thenAcceptAsync { |
||||||
|
logger.info("finished to save logcat. exitValue: ${it.exitValue()}, path: $filePath") |
||||||
|
} |
||||||
|
dumpProcess = process |
||||||
|
Thread.sleep(TimeUnit.HOURS.toMillis(2)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun stopDump() { |
||||||
|
dumpProcess?.destroy() |
||||||
|
} |
||||||
|
|
||||||
|
companion object : KLogging() |
||||||
|
} |
||||||
@ -0,0 +1,98 @@ |
|||||||
|
package tv.anypoint.proxy.service |
||||||
|
|
||||||
|
import kotlinx.serialization.encodeToString |
||||||
|
import kotlinx.serialization.json.Json |
||||||
|
import mu.KLogging |
||||||
|
import org.springframework.boot.autoconfigure.web.ServerProperties |
||||||
|
import org.springframework.stereotype.Service |
||||||
|
import tv.anypoint.ApplicationProperties |
||||||
|
import tv.anypoint.dsl.exception.httpValidationError |
||||||
|
import tv.anypoint.dsl.model.TcContext |
||||||
|
import tv.anypoint.proxy.adapter.AssignAdapter |
||||||
|
import tv.anypoint.proxy.adapter.AuthAdapter |
||||||
|
import tv.anypoint.proxy.model.agent.AuthRequest |
||||||
|
import tv.anypoint.proxy.model.agent.AuthResponse |
||||||
|
import tv.anypoint.proxy.model.agent.Endpoints |
||||||
|
import tv.anypoint.proxy.model.agent.ad.AdsResponse |
||||||
|
import tv.anypoint.proxy.model.agent.ad.AdsSyncRequest |
||||||
|
|
||||||
|
@Service |
||||||
|
class ProxyDeviceService( |
||||||
|
private val json: Json, |
||||||
|
private val serverIp: String, |
||||||
|
private val serverProperties: ServerProperties, |
||||||
|
private val applicationProperties: ApplicationProperties, |
||||||
|
private val authAdapter: AuthAdapter, |
||||||
|
private val assignAdapter: AssignAdapter, |
||||||
|
private val tcContext: TcContext, |
||||||
|
private val logcatService: LogcatService |
||||||
|
) { |
||||||
|
fun auth(request: AuthRequest): AuthResponse { |
||||||
|
return try { |
||||||
|
val authResponse = authAdapter.auth( |
||||||
|
request = request |
||||||
|
).execute().body()!! |
||||||
|
authResponse.endpoints.redirectToProxyServer( |
||||||
|
serverIp = serverIp, |
||||||
|
serverPort = serverProperties.port, |
||||||
|
pushServerPort = applicationProperties.pushServerPort |
||||||
|
) |
||||||
|
if (!tcContext.tc.auth.validate(authResponse)) { |
||||||
|
throw httpValidationError(authResponse) |
||||||
|
} |
||||||
|
val response = tcContext.tc.auth.convert(authResponse) |
||||||
|
tcContext.tc.authResponse = response |
||||||
|
logger.info("auth response: ${json.encodeToString(response)}") |
||||||
|
|
||||||
|
if (tcContext.tc.reboot) { |
||||||
|
logcatService.startDump() |
||||||
|
// TODO: logcat 에서 `auth response body` 를 기다린 다음에 adb ADS_SYNC 호출 |
||||||
|
} else { |
||||||
|
// TODO: adb ADS_SYNC 호출 |
||||||
|
} |
||||||
|
response |
||||||
|
} catch(e: Exception) { |
||||||
|
logger.error("failed to auth.", e) |
||||||
|
throw RuntimeException("failed to auth.") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun Endpoints.redirectToProxyServer( |
||||||
|
serverIp: String, |
||||||
|
serverPort: Int, |
||||||
|
pushServerPort: Int |
||||||
|
) { |
||||||
|
val serverEndpoint = "http://$serverIp:$serverPort" |
||||||
|
val pushServerEndpoint = "$serverIp:$pushServerPort" |
||||||
|
this.auth = serverEndpoint |
||||||
|
this.requestAds = serverEndpoint |
||||||
|
this.adSyncResult = serverEndpoint |
||||||
|
this.appLog = serverEndpoint |
||||||
|
this.event = serverEndpoint |
||||||
|
// this.pushServers = listOf(pushServerEndpoint) |
||||||
|
this.ntpServers = emptyList() |
||||||
|
this.stateLog = serverEndpoint |
||||||
|
this.impressionLog = serverEndpoint |
||||||
|
this.assetRequest = serverEndpoint |
||||||
|
this.proxyAdLog = serverEndpoint |
||||||
|
} |
||||||
|
|
||||||
|
fun ads(deviceId: Long, freeStorage: Long?, usedStorage: Long?): AdsResponse { |
||||||
|
val adsResponse = assignAdapter.ads( |
||||||
|
deviceId = deviceId, |
||||||
|
freeStorage = freeStorage ?: 0, |
||||||
|
usedStorage = usedStorage ?: 0 |
||||||
|
).execute().body()!! |
||||||
|
val response = tcContext.tc.ads.convert(adsResponse) |
||||||
|
tcContext.tc.adsResponse = response |
||||||
|
logger.info("ads response: ${json.encodeToString(response)}") |
||||||
|
|
||||||
|
return response |
||||||
|
} |
||||||
|
|
||||||
|
fun adSync(request: AdsSyncRequest) { |
||||||
|
// nothing to do. |
||||||
|
} |
||||||
|
|
||||||
|
companion object : KLogging() |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
package tv.anypoint.proxy.service |
||||||
|
|
||||||
|
import tv.anypoint.proxy.model.agent.Cue |
||||||
|
import tv.anypoint.proxy.model.push.PushCommandType |
||||||
|
|
||||||
|
interface PushServerInterface { |
||||||
|
fun sendCommand(commandType: PushCommandType): Boolean |
||||||
|
|
||||||
|
fun sendCue(cue: Cue): Boolean |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
package tv.anypoint.proxy.service |
||||||
|
|
||||||
|
import org.springframework.stereotype.Service |
||||||
|
import tv.anypoint.ApplicationProperties |
||||||
|
import tv.anypoint.proxy.model.agent.Cue |
||||||
|
import tv.anypoint.proxy.model.push.PushCommandType |
||||||
|
|
||||||
|
@Service |
||||||
|
class PushServerService( |
||||||
|
private val applicationProperties: ApplicationProperties |
||||||
|
) : PushServerInterface { |
||||||
|
override fun sendCommand(commandType: PushCommandType): Boolean { |
||||||
|
applicationProperties.pushServerPort |
||||||
|
TODO("Not Implemented") |
||||||
|
} |
||||||
|
|
||||||
|
override fun sendCue(cue: Cue): Boolean { |
||||||
|
applicationProperties.pushServerPort |
||||||
|
TODO("Not Implemented") |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
package tv.anypoint.proxy.service |
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.web.ServerProperties |
||||||
|
import org.springframework.stereotype.Service |
||||||
|
import se.vidstige.jadb.JadbDevice |
||||||
|
import tv.anypoint.dsl.model.Tc |
||||||
|
import tv.anypoint.dsl.model.TcContext |
||||||
|
import tv.anypoint.proxy.model.push.PushCommandType |
||||||
|
import tv.anypoint.proxy.shell.ShellController |
||||||
|
import tv.anypoint.proxy.util.changeAuthEndpoint |
||||||
|
import tv.anypoint.proxy.util.reboot |
||||||
|
import tv.anypoint.proxy.util.sendNumberInput |
||||||
|
|
||||||
|
@Service |
||||||
|
class StbService( |
||||||
|
private val serverIp: String, |
||||||
|
private val jadbDevice: JadbDevice, |
||||||
|
private val tcContext: TcContext, |
||||||
|
private val serverProperties: ServerProperties, |
||||||
|
private val logcatService: LogcatService, |
||||||
|
private val pushServerService: PushServerService |
||||||
|
) { |
||||||
|
fun start( |
||||||
|
restartStb: Boolean, |
||||||
|
serverIp: String = this.serverIp, |
||||||
|
serverPort: Int = serverProperties.port |
||||||
|
) { |
||||||
|
logcatService.startDump() |
||||||
|
tcContext.tc = Tc().apply { |
||||||
|
this.number = "MANUAL" |
||||||
|
this.reboot = restartStb |
||||||
|
} |
||||||
|
changeAuthEndpoint(serverIp = serverIp, serverPort = serverPort) |
||||||
|
if (restartStb) { |
||||||
|
jadbDevice.reboot() |
||||||
|
} else { |
||||||
|
pushServerService.sendCommand(PushCommandType.UPDATE_SYSTEM_INFO) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun changeAuthEndpoint( |
||||||
|
serverIp: String, |
||||||
|
serverPort: Int |
||||||
|
) { |
||||||
|
jadbDevice.changeAuthEndpoint(serverIp = serverIp, serverPort = serverPort) |
||||||
|
} |
||||||
|
|
||||||
|
fun changeChannelNumber(channelNumber: String) { |
||||||
|
channelNumber.forEach { c -> |
||||||
|
jadbDevice.sendNumberInput(c) |
||||||
|
} |
||||||
|
ShellController.logger.info("changed ch number: $channelNumber") |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
package tv.anypoint.proxy.shell |
||||||
|
|
||||||
|
import mu.KLogging |
||||||
|
import org.springframework.shell.standard.ShellComponent |
||||||
|
import org.springframework.shell.standard.ShellMethod |
||||||
|
import tv.anypoint.proxy.service.StbService |
||||||
|
|
||||||
|
@ShellComponent |
||||||
|
class ShellController( |
||||||
|
private val stbService: StbService |
||||||
|
) { |
||||||
|
|
||||||
|
/** |
||||||
|
* 채널 변경 |
||||||
|
*/ |
||||||
|
@ShellMethod |
||||||
|
fun ch(channelNumber: String) { |
||||||
|
stbService.changeChannelNumber(channelNumber) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* STB 에 접속 |
||||||
|
* ip, port 지정하지 않을 경우 설정된 STB 에 접속 |
||||||
|
*/ |
||||||
|
@ShellMethod |
||||||
|
fun start( |
||||||
|
restart: Boolean = false, |
||||||
|
ip: String? = null, |
||||||
|
port: Int? = null |
||||||
|
) { |
||||||
|
when { |
||||||
|
ip == null && port == null -> stbService.start(restartStb = restart) |
||||||
|
ip != null && port != null -> stbService.start(restartStb = restart, serverIp = ip, serverPort = port) |
||||||
|
ip != null && port == null -> stbService.start(restartStb = restart, serverIp = ip) |
||||||
|
ip == null && port != null -> stbService.start(restartStb = restart, serverPort = port) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object : KLogging() |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
package tv.anypoint.proxy.util |
||||||
|
|
||||||
|
import java.net.Inet4Address |
||||||
|
import java.net.InetSocketAddress |
||||||
|
|
||||||
|
object JadbDeviceSerialUtil { |
||||||
|
|
||||||
|
private val serialRegex = Regex("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})") |
||||||
|
|
||||||
|
fun addressToId(hostAddress: String, port: Int) = "$hostAddress:$port" |
||||||
|
fun addressToId(socketAddress: InetSocketAddress) = addressToId(socketAddress.address.hostAddress, socketAddress.port) |
||||||
|
|
||||||
|
fun serialToSocketAddress(serial: String) : InetSocketAddress? { |
||||||
|
val matchResult = serialRegex.matchEntire(serial) ?: return null |
||||||
|
val address = Inet4Address.getByName(matchResult.groupValues[1]) |
||||||
|
val port = Integer.parseInt(matchResult.groupValues[2]) |
||||||
|
return InetSocketAddress(address, port) |
||||||
|
} |
||||||
|
|
||||||
|
fun serialToAddress(serial: String) = serialToSocketAddress(serial)?.address |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,88 @@ |
|||||||
|
package tv.anypoint.proxy.util |
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils |
||||||
|
import org.slf4j.Logger |
||||||
|
import org.slf4j.LoggerFactory |
||||||
|
import se.vidstige.jadb.JadbConnection |
||||||
|
import se.vidstige.jadb.JadbDevice |
||||||
|
import tv.anypoint.proxy.model.adb.ChangeCommand |
||||||
|
import tv.anypoint.proxy.model.adb.ExtraKey |
||||||
|
import tv.anypoint.proxy.model.adb.IntentAction |
||||||
|
import java.net.InetSocketAddress |
||||||
|
import java.nio.charset.StandardCharsets |
||||||
|
|
||||||
|
private val logger: Logger = LoggerFactory.getLogger("tv.anypoint.proxy.util.StbUtilKt") |
||||||
|
|
||||||
|
fun getDevice(stbIp: String, stbPort: Int): JadbDevice { |
||||||
|
try { |
||||||
|
val socketAddress = InetSocketAddress(stbIp, stbPort) |
||||||
|
val jadbc = JadbConnection() |
||||||
|
try { |
||||||
|
jadbc.connectToTcpDevice(socketAddress) |
||||||
|
} catch (e: Exception) { |
||||||
|
logger.error("Can't connect adb to $socketAddress, Reason -> ${e.message}") |
||||||
|
} |
||||||
|
return jadbc.devices.firstOrNull { it.serial in JadbDeviceSerialUtil.addressToId(socketAddress) } |
||||||
|
?: throw RuntimeException("adb server not running!") |
||||||
|
} catch (e: Exception) { |
||||||
|
throw RuntimeException("adb server not running!", e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sends broadcast command to the device to change the auth endpoint to the server. |
||||||
|
*/ |
||||||
|
fun JadbDevice.changeAuthEndpoint( |
||||||
|
serverIp: String, |
||||||
|
serverPort: Int |
||||||
|
) { |
||||||
|
val proxyEndpoint = "http://$serverIp:$serverPort" |
||||||
|
executeShellDebug( |
||||||
|
a = tv.anypoint.proxy.model.adb.IntentAction.CHANGE_TEST_PROPERTY, |
||||||
|
es = mapOf( |
||||||
|
tv.anypoint.proxy.model.adb.ExtraKey.CHANGE_COMMAND to tv.anypoint.proxy.model.adb.ChangeCommand.CHANGE_API_ENDPOINT.name, |
||||||
|
tv.anypoint.proxy.model.adb.ExtraKey.API_ENDPOINT to proxyEndpoint |
||||||
|
) |
||||||
|
) |
||||||
|
logger.info("changed auth endpoint to proxy endpoint $proxyEndpoint") |
||||||
|
} |
||||||
|
|
||||||
|
fun JadbDevice.executeShellDebug(a: tv.anypoint.proxy.model.adb.IntentAction, es: Map<tv.anypoint.proxy.model.adb.ExtraKey, String>) { |
||||||
|
val shellContentBuilder = StringBuilder() |
||||||
|
shellContentBuilder.append("am broadcast ") |
||||||
|
shellContentBuilder.append("-a ${a.string} ") |
||||||
|
es.forEach { (k, v) -> |
||||||
|
shellContentBuilder.append("--es ${k.string} $v ") |
||||||
|
} |
||||||
|
this.executeShellDebug(shellContentBuilder.toString()) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sends number as an input to the device, just like |
||||||
|
* pressing a number button in the remote controller. |
||||||
|
*/ |
||||||
|
fun JadbDevice.sendNumberInput(numberButton: Char) { |
||||||
|
if (numberButton < '0' || numberButton > '9') return |
||||||
|
|
||||||
|
val shellContent = "input keyevent KEYCODE_$numberButton" |
||||||
|
this.executeShellDebug(shellContent) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sends shell to the device |
||||||
|
*/ |
||||||
|
fun JadbDevice.executeShellDebug(content: String) { |
||||||
|
logger.info("[[ cmd --> {} ]] {}", this.serial, content) |
||||||
|
val inputStream = this.executeShell(content) |
||||||
|
val commandResult = IOUtils.toString(inputStream, StandardCharsets.UTF_8) |
||||||
|
for (line in commandResult.split('\n')) { |
||||||
|
logger.info("[[ cmd <-- {} ]] {}", this.serial, line.removeNewLines()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sends reboot command to the device. |
||||||
|
*/ |
||||||
|
fun JadbDevice.reboot() { |
||||||
|
executeShellDebug("reboot") |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
package tv.anypoint.proxy.util |
||||||
|
|
||||||
|
fun String.removeNewLines() = this.replace("\n", "") |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
package tv.anypoint.proxy.web |
||||||
|
|
||||||
|
import mu.KLogging |
||||||
|
import org.springframework.web.bind.annotation.GetMapping |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping |
||||||
|
import org.springframework.web.bind.annotation.RestController |
||||||
|
|
||||||
|
@RequestMapping("/test") |
||||||
|
@RestController |
||||||
|
class TestController { |
||||||
|
@GetMapping |
||||||
|
fun testGet(): Boolean { |
||||||
|
logger.info("GET /test") |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
companion object : KLogging() |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
import java.io.BufferedReader |
||||||
|
import java.io.FileReader |
||||||
|
import java.io.IOException |
||||||
|
import java.io.LineNumberReader |
||||||
|
|
||||||
|
class Test { |
||||||
|
@Throws(IOException::class) |
||||||
|
fun test() { |
||||||
|
val lnr = LineNumberReader(BufferedReader(FileReader("all.log"))) |
||||||
|
var line: String? |
||||||
|
while ((lnr.readLine().also { line = it }) != null) { |
||||||
|
println(line) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
package tv.anypoint.dsl |
||||||
|
|
||||||
|
import io.kotest.core.spec.style.BehaviorSpec |
||||||
|
import io.kotest.core.spec.style.FunSpec |
||||||
|
import io.kotest.matchers.longs.shouldBeLessThanOrEqual |
||||||
|
import io.kotest.matchers.shouldBe |
||||||
|
import kotlinx.coroutines.delay |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import kotlinx.serialization.encodeToString |
||||||
|
import kotlinx.serialization.json.Json |
||||||
|
import mu.KLogging |
||||||
|
import tv.anypoint.dsl.model.Log |
||||||
|
import java.io.File |
||||||
|
import java.time.Duration |
||||||
|
import java.time.LocalDateTime |
||||||
|
|
||||||
|
class DslKtTest : FunSpec() { |
||||||
|
init { |
||||||
|
test("Log.expected()") { |
||||||
|
// given |
||||||
|
val file = File("./test.log") |
||||||
|
val writer = file.writer() |
||||||
|
val startedAt = LocalDateTime.now() |
||||||
|
val test5 = Log("./test.log") |
||||||
|
val test7 = Log("./test.log") |
||||||
|
|
||||||
|
object : Thread() { |
||||||
|
override fun run() { |
||||||
|
(1..20).forEach { |
||||||
|
logger.info("print test $it") |
||||||
|
writer.write("test $it${System.lineSeparator()}") |
||||||
|
writer.flush() |
||||||
|
sleep(500) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.start() |
||||||
|
|
||||||
|
// when |
||||||
|
test5.expected("test 5") |
||||||
|
test7.expected("test 7") |
||||||
|
|
||||||
|
// then |
||||||
|
test5.cursor shouldBe 4 |
||||||
|
test7.cursor shouldBe 6 |
||||||
|
Duration.between(startedAt, LocalDateTime.now()).seconds shouldBeLessThanOrEqual 10_000 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object : KLogging() |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
package tv.anypoint.proxy.adapter |
||||||
|
|
||||||
|
import io.kotest.core.spec.style.BehaviorSpec |
||||||
|
import io.kotest.core.spec.style.FunSpec |
||||||
|
import io.kotest.matchers.shouldBe |
||||||
|
import mu.KLogging |
||||||
|
import org.springframework.boot.test.context.SpringBootTest |
||||||
|
import tv.anypoint.proxy.model.agent.AuthRequest |
||||||
|
|
||||||
|
@SpringBootTest |
||||||
|
class AuthAdapterTest( |
||||||
|
private val authAdapter: AuthAdapter |
||||||
|
) : BehaviorSpec() { |
||||||
|
init { |
||||||
|
Given("request") { |
||||||
|
val request = AuthRequest( |
||||||
|
fingerPrint = "", |
||||||
|
soId = 1, |
||||||
|
uuid = "43dd41a2-111b-48c8-b840-629978714f0b", |
||||||
|
freeStorage = 2519707648, |
||||||
|
usedStorage = 294611792, |
||||||
|
cachedStorage = 203884308, |
||||||
|
modelName = "BID-AI100", |
||||||
|
firmwareBuildDate = "2024.04.02", |
||||||
|
firmwareVer = "16.540.22", |
||||||
|
fullFirmwareVer = "16.540.22-0000", |
||||||
|
appVersion = "3.9.4-RC59_20230712", |
||||||
|
platformAdId = "63c33b8c-f5d5-47b3-9e7d-f06342fb65fe", |
||||||
|
sdkVersion = "2.0.1-RC12" |
||||||
|
) |
||||||
|
|
||||||
|
When("call auth API") { |
||||||
|
val actual = authAdapter.auth(request) |
||||||
|
.execute().body() |
||||||
|
logger.info("actual: $actual") |
||||||
|
Then("actual is not null") { |
||||||
|
1 shouldBe 1 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object : KLogging() |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
package tv.anypoint.proxy.service |
||||||
|
|
||||||
|
import io.kotest.core.spec.style.FunSpec |
||||||
|
import org.springframework.boot.test.context.SpringBootTest |
||||||
|
|
||||||
|
@SpringBootTest |
||||||
|
class LogcatServiceTest( |
||||||
|
logcatService: LogcatService |
||||||
|
) : FunSpec({ |
||||||
|
test("start") { |
||||||
|
logcatService.startDump() |
||||||
|
Thread.sleep(5_000) |
||||||
|
logcatService.stopDump() |
||||||
|
} |
||||||
|
}) |
||||||
Loading…
Reference in new issue