All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ai.platon.pulsar.common.proxy.ProxyPoolManager.kt Maven / Gradle / Ivy

package ai.platon.pulsar.common.proxy

import ai.platon.pulsar.common.AppContext
import ai.platon.pulsar.common.AppFiles
import ai.platon.pulsar.common.AppPaths
import ai.platon.pulsar.common.AppPaths.AVAILABLE_PROVIDER_DIR
import ai.platon.pulsar.common.AppPaths.ENABLED_PROVIDER_DIR
import ai.platon.pulsar.common.FileCommand
import ai.platon.pulsar.common.config.AppConstants
import ai.platon.pulsar.common.config.CapabilityTypes
import ai.platon.pulsar.common.config.ImmutableConfig
import org.apache.commons.io.FileUtils
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration
import java.time.Instant
import java.util.concurrent.ConcurrentSkipListMap
import java.util.concurrent.ConcurrentSkipListSet
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger

open class ProxyPoolManager(
        val proxyPool: ProxyPool,
        private val conf: ImmutableConfig
): AutoCloseable {
    // Set the active proxy idle, for test purpose
    private val isForceIdle get() = FileCommand.check(AppConstants.CMD_PROXY_FORCE_IDLE, 15)

    var lastActiveTime = Instant.EPOCH
    var idleTimeout = conf.getDuration(CapabilityTypes.PROXY_IDLE_TIMEOUT, Duration.ofMinutes(10))
    val idleTime get() = Duration.between(lastActiveTime, Instant.now())
    open val isIdle get() = (numRunningTasks.get() == 0 && idleTime > idleTimeout) || isForceIdle

    val numRunningTasks = AtomicInteger()
    var statusString: String = ""
    var verbose = false

    val activeProxyEntries = ConcurrentSkipListMap()
    val workingProxyEntries = ConcurrentSkipListSet()

    val isEnabled get() = isProxyEnabled(conf)
    val isDisabled get() = !isEnabled

    private val closed = AtomicBoolean()
    val isActive get() = !closed.get()

    /**
     * Run the task, if the proxy is disabled, call the innovation directly
     * */
    @Throws(NoProxyException::class)
    open suspend fun  runWith(proxyEntry: ProxyEntry?, task: suspend () -> R): R {
        return if (isDisabled) task() else runWith0(proxyEntry, task)
    }

    /**
     * Run the task with the proxy
     * */
    @Throws(NoProxyException::class)
    private suspend fun  runWith0(proxyEntry: ProxyEntry?, task: suspend () -> R): R {
        return try {
            lastActiveTime = Instant.now()
            proxyEntry?.also {
                it.lastActiveTime = lastActiveTime
                workingProxyEntries.add(it)
            }
            numRunningTasks.incrementAndGet()
            task()
        } finally {
            lastActiveTime = Instant.now()
            proxyEntry?.also {
                it.lastActiveTime = lastActiveTime
                workingProxyEntries.remove(it)
            }
            numRunningTasks.decrementAndGet()
        }
    }

    /**
     * Take off the proxy if it is active, and the monitor will choose the next proxy to connect
     * */
    open fun takeOff(excludedProxy: ProxyEntry, ban: Boolean) {}

    override fun toString(): String {
        return statusString
    }

    override fun close() {

    }

    companion object {
        private const val PROXY_PROVIDER_FILE_NAME = "proxy.providers.txt"
        private val DEFAULT_PROXY_PROVIDER_FILES = arrayOf(AppContext.USER_HOME, AppContext.TMP_DIR)
            .map { Paths.get(it) }
            .map { it.resolve(PROXY_PROVIDER_FILE_NAME) }

        private val PROXY_FILE_WATCH_INTERVAL = Duration.ofSeconds(30)
        private var providerDirLastWatchTime = Instant.EPOCH
        private var numEnabledProviderFiles = 0L

        /**
         * Proxy system can be enabled/disabled at runtime
         * Check if the proxy is enabled.
         *
         * The proxy system is enabled if:
         *
         * 1. PROXY_USE_PROXY is set to "yes" or "true"
         * 2. PROXY_USE_PROXY is not set
         *    1. PROXY_ENABLE_DEFAULT_PROVIDERS is "true"
         *    2. and there are proxy providers in AVAILABLE_PROVIDER_DIR
         * */
        fun isProxyEnabled(conf: ImmutableConfig): Boolean {
            val useProxy = conf[CapabilityTypes.PROXY_USE_PROXY]
            when (useProxy) {
                "yes", "true" -> return true
                "no", "false" -> return false
            }

            if (conf.getBoolean(CapabilityTypes.PROXY_ENABLE_DEFAULT_PROVIDERS, false)) {
                enableDefaultProviders()
            }

            // if no one set the proxy availability explicitly, but we have providers, use it
            return hasEnabledProvider()
        }

        @Synchronized
        @Throws(IOException::class)
        fun hasEnabledProvider(): Boolean {
            val now = Instant.now()

            if (Duration.between(providerDirLastWatchTime, now) > PROXY_FILE_WATCH_INTERVAL) {
                providerDirLastWatchTime = now
                numEnabledProviderFiles = try {
                    Files.list(AppPaths.ENABLED_PROVIDER_DIR).filter { Files.isRegularFile(it) }.count()
                } catch (e: Throwable) { 0 }
            }

            return numEnabledProviderFiles > 0
        }

        fun enableProxy(): Companion {
            System.setProperty(CapabilityTypes.PROXY_USE_PROXY, "yes")
            return this
        }

        fun disableProxy(): Companion {
            System.setProperty(CapabilityTypes.PROXY_USE_PROXY, "no")
            return this
        }

        @Synchronized
        @Throws(IOException::class)
        fun enableDefaultProviders(): Companion {
            DEFAULT_PROXY_PROVIDER_FILES.mapNotNull { it.takeIf { Files.exists(it) } }.forEach {
                FileUtils.copyFileToDirectory(it.toFile(), AVAILABLE_PROVIDER_DIR.toFile())
            }
            AVAILABLE_PROVIDER_DIR.mapNotNull { it.takeIf { Files.exists(it) } }.forEach { enableProvider(it) }

            return this
        }

        @Synchronized
        @Throws(IOException::class)
        fun enableProvider(providerPath: Path): Companion {
            val filename = providerPath.fileName
            val target = ENABLED_PROVIDER_DIR.resolve(filename)
            Files.deleteIfExists(target)
            if (AppFiles.supportSymbolicLink(target)) {
                Files.createSymbolicLink(target, providerPath)
            } else {
                Files.copy(providerPath, target)
            }

            return this
        }

        @Synchronized
        @Throws(IOException::class)
        fun disableProviders(): Companion {
            Files.list(ENABLED_PROVIDER_DIR)
                    .filter { Files.isRegularFile(it) || Files.isSymbolicLink(it) }
                    .forEach { Files.delete(it) }
            return this
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy