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 { |
||||
CHANGE_API_ENDPOINT, |
||||
@ -1,8 +1,8 @@
|
||||
package tv.anypoint.domain.adb |
||||
package tv.anypoint.proxy.model.adb |
||||
|
||||
enum class ExtraKey(val string: String) { |
||||
EXTRA_JSON("EXTRA_JSON"), |
||||
CHANGE_COMMAND("change.command"), |
||||
API_ENDPOINT("api.endpoint"), |
||||
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) { |
||||
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( |
||||
var fingerPrint: String = "", |
||||
val soId: Int = 0, |
||||
@ -1,4 +1,4 @@
|
||||
package tv.anypoint.domain.agent |
||||
package tv.anypoint.proxy.model.agent |
||||
|
||||
data class Cue( |
||||
var id: Long = 0, |
||||
@ -1,4 +1,4 @@
|
||||
package tv.anypoint.domain.agent |
||||
package tv.anypoint.proxy.model.agent |
||||
|
||||
enum class CueOwner { |
||||
PP, |
||||
@ -1,7 +1,7 @@
|
||||
package tv.anypoint.domain.agent |
||||
package tv.anypoint.proxy.model.agent |
||||
|
||||
enum class PlayType { |
||||
DNP, |
||||
LAZY_DNP, |
||||
STREAMING |
||||
} |
||||
} |
||||
@ -1,6 +1,9 @@
|
||||
package tv.anypoint.domain.agent |
||||
package tv.anypoint.proxy.model.agent |
||||
|
||||
import kotlinx.serialization.Serializable |
||||
|
||||
@Serializable |
||||
data class PlayerStateCheckParam( |
||||
val playerStateCheckDuration: Int = 1500, |
||||
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( |
||||
val id: Int, |
||||
val delay: Int = 0, |
||||
@ -1,4 +1,4 @@
|
||||
package tv.anypoint.domain.agent |
||||
package tv.anypoint.proxy.model.agent |
||||
|
||||
data class StateChangeLog( |
||||
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( |
||||
val status: AdListResponseStatus = AdListResponseStatus.OK, |
||||
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( |
||||
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( |
||||
var assetId: Long = 0L, |
||||
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( |
||||
val targetType: 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( |
||||
val hours: 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