This repository has been archived on 2026-04-23. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Practice-Part/src/main/kotlin/Main.kt
2026-04-17 09:18:06 +05:00

656 lines
23 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ru.thiflict
import kotlinx.coroutines.*
import ru.thiflict.DebugMode.isDebug
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import kotlin.system.exitProcess
import java.util.*
import java.util.concurrent.*
import kotlin.time.Duration.Companion.milliseconds
// Конфигурация GPIO (BCM номера)
object PinConfig {
const val GREEN_LED = 17
const val YELLOW_LED = 27
const val RED_LED = 22
const val BUTTON = 24
}
object DebugMode {
var isDebug = false
fun enableDebugging() {
isDebug = true
}
}
// Состояния системы
enum class SystemState {
IDLE, SCANNING, CLEAN, INFECTED, ERROR, INIT, SAMBA
}
class USBVirusScanner {
private var currentState = SystemState.INIT
private var scanJob: Job? = null
private var indicationJob: Job? = null
private var isRunning = true
private lateinit var greenLed: GPIOController
private lateinit var yellowLed: GPIOController
private lateinit var redLed: GPIOController
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun initialize() {
println(if (isDebug) "Инициализация..." else "")
try {
// Проверяем доступность gpiod
checkLibgpiod()
greenLed = GPIOController(PinConfig.GREEN_LED)
yellowLed = GPIOController(PinConfig.YELLOW_LED)
redLed = GPIOController(PinConfig.RED_LED)
// Выключаем все светодиоды
greenLed.setValue(0)
yellowLed.setValue(0)
redLed.setValue(0)
// Запускаем индикацию состояния
startStateIndication()
antivirusDatabaseUpdater()
Thread.sleep(1000)
println(if (isDebug) "Инициализация завершена." else "")
currentState = SystemState.IDLE
} catch (e: Exception) {
currentState = SystemState.ERROR
println(if (isDebug) "❌ Ошибка инициализации: ${e.message}" else "")
e.printStackTrace()
throw e
}
}
private fun checkLibgpiod() {
try {
val process = ProcessBuilder("gpiodetect").start()
val exitCode = process.waitFor()
if (exitCode != 0) {
throw Exception(if (isDebug) "gpiodetect не найден" else "")
}
val output = process.inputStream.bufferedReader().readText()
println(if (isDebug) "✅ libgpiod найден" else "")
if (output.isNotBlank()) {
println(if (isDebug) "Доступные чипы: ${output.trim()}" else "")
}
} catch (e: Exception) {
throw Exception(if (isDebug) "libgpiod Не установлен!" else "")
}
}
fun buttonPress(defaultButtonState: Boolean): Boolean {
var finalButtonState = defaultButtonState
println(if (isDebug) "Начало finalButtonState: $finalButtonState" else "")
val buttonListener = ButtonListener()
val initialState = buttonListener.startListening { isPressed ->
if (isPressed) {
finalButtonState = true
}
}
println(if (isDebug) "finalButtonState: $finalButtonState" else "")
return finalButtonState
}
private fun startStateIndication() {
indicationJob = scope.launch {
while (isRunning) {
when (currentState) {
SystemState.INIT -> {
greenLed.setValue(1)
yellowLed.setValue(1)
redLed.setValue(1)
delay(200.milliseconds)
greenLed.setValue(0)
yellowLed.setValue(0)
redLed.setValue(0)
delay(200.milliseconds)
}
SystemState.IDLE -> {
// Зелёный медленно мигает
blinkLed(greenLed, 1000)
yellowLed.setValue(0)
redLed.setValue(0)
}
SystemState.SCANNING -> {
// Жёлтый быстро мигает
greenLed.setValue(0)
blinkLed(yellowLed, 150)
redLed.setValue(0)
}
SystemState.CLEAN -> {
// Зелёный горит
greenLed.setValue(1)
yellowLed.setValue(0)
redLed.setValue(0)
delay(100.milliseconds)
}
SystemState.INFECTED -> {
// Красный горит
greenLed.setValue(0)
yellowLed.setValue(0)
redLed.setValue(1)
delay(100.milliseconds)
}
SystemState.ERROR -> {
// Красный мигает
greenLed.setValue(0)
yellowLed.setValue(0)
blinkLed(redLed, 500)
}
SystemState.SAMBA -> {
blinkLed(greenLed, 200)
blinkLed(yellowLed, 200)
redLed.setValue(0)
}
}
}
}
}
private suspend fun blinkLed(led: GPIOController, intervalMs: Long) {
led.setValue(1)
delay((intervalMs / 2).milliseconds)
led.setValue(0)
delay((intervalMs / 2).milliseconds)
}
fun startScan() {
if (scanJob?.isActive == true) {
println(if (isDebug) "Сканирование уже выполняется" else "")
return
}
scanJob = scope.launch {
currentState = SystemState.SCANNING
println(if (isDebug) "=== Начало сканирования ===" else "")
try {
// Поиск USB-устройств
val usbDevices = findUSBDevices()
if (usbDevices.isEmpty()) {
println(if (isDebug) "Устройства не найдены" else "")
currentState = SystemState.ERROR
delay(5000.milliseconds)
currentState = SystemState.IDLE
return@launch
}
println(if (isDebug) "📀 Найдены USB-устройства:" else "")
usbDevices.forEach { device ->
println(" - ${device.mountPoint}")
}
println()
var hasVirus = false
val scanResults = mutableListOf<ScanResult>()
for (device in usbDevices) {
println(if (isDebug) "🔍 Сканирование: ${device.mountPoint}" else "")
val result = scanWithClamAV(device.mountPoint)
scanResults.add(result)
if (result.isInfected) {
hasVirus = true
println(if (isDebug) "⚠️ ВНИМАНИЕ! Найдены угрозы!" else "")
result.infectedFiles.take(10).forEach { file ->
println(" 🦠 ${file.take(100)}")
}
if (result.infectedFiles.size > 10) {
println(if (isDebug) " ... и ещё ${result.infectedFiles.size - 10} файлов" else "")
}
unmount()
} else if (result.error != null) {
println(if (isDebug) "❌ Ошибка сканирования: ${result.error}" else "")
} else {
println(if (isDebug) "✅ Сканирование завершено: угроз не найдено" else "")
}
println()
}
// Сохраняем лог
saveScanLog(scanResults)
currentState = if (hasVirus) {
SystemState.INFECTED
} else {
SystemState.CLEAN
}
val message = if (hasVirus) {
if (isDebug) "🦠 Найдены вирусы! Красный LED горит" else ""
} else {
if (isDebug) "✅ Флешка чиста! Зелёный LED горит" else ""
}
println(if (isDebug) message else "")
// Держим состояние 5 секунд
delay(10000.milliseconds)
if (!hasVirus) {
currentState = SystemState.SAMBA
sambaStartSharing()
val buttonListener = ButtonListener()
var isEventHappened = false
buttonListener.addLongPressListener { event ->
isEventHappened = true
}
buttonListener.startListening()
while (!isEventHappened) {
delay(500.milliseconds)
}
}
currentState = SystemState.IDLE
} catch (e: Exception) {
println(if (isDebug) "❌ Критическая ошибка: ${e.message}" else "")
e.printStackTrace()
currentState = SystemState.ERROR
delay(10000.milliseconds)
currentState = SystemState.IDLE
}
println(if (isDebug) "=== Сканирование завершено ===\n" else "")
}
}
private fun findUSBDevices(): List<USBDevice> {
val devices = mutableListOf<USBDevice>()
try {
// Проверяем /media директории
val mediaDirs = listOf("/media", "/media/pi")
for (mediaDirPath in mediaDirs) {
val mediaDir = File(mediaDirPath)
if (mediaDir.exists() && mediaDir.isDirectory) {
mediaDir.listFiles()?.forEach { dir ->
if (dir.isDirectory && dir.canRead()) {
// Проверяем, что это не системная директория
val isSystemDir = dir.name.startsWith(".") ||
dir.name == "root" ||
dir.name == "boot"
if (!isSystemDir && devices.none { it.mountPoint == dir.absolutePath }) {
// Дополнительная проверка: есть ли файлы в директории
val hasFiles = dir.listFiles()?.isNotEmpty() == true
if (hasFiles) {
devices.add(USBDevice("usb", dir.absolutePath))
}
}
}
}
}
}
// Проверяем /proc/mounts для дополнительных точек монтирования
val mountsFile = File("/proc/mounts")
if (mountsFile.exists()) {
mountsFile.readLines().forEach { line ->
val parts = line.split(" ")
if (parts.size >= 2) {
val mountPoint = parts[1]
if ((mountPoint.startsWith("/media")) &&
File(mountPoint).exists() &&
devices.none { it.mountPoint == mountPoint }
) {
devices.add(USBDevice("auto", mountPoint))
}
}
}
}
} catch (e: Exception) {
println(if (isDebug) "Ошибка при поиске USB-устройств: ${e.message}" else "")
}
return devices.distinctBy { it.mountPoint }
}
private fun scanWithClamAV(path: String): ScanResult {
val infectedFiles = mutableListOf<String>()
val virusNames = mutableMapOf<String, String>()
try {
// Проверяем установлен ли ClamAV
val checkProcess = ProcessBuilder("which", "clamscan").start()
val checkExitCode = checkProcess.waitFor()
if (checkExitCode != 0) {
return ScanResult(
isInfected = false,
infectedFiles = emptyList(),
virusNames = emptyMap(),
exitCode = -1,
error = if (isDebug) "ClamAV не установлен. Установите: sudo apt install clamav" else ""
)
}
// Проверяем существование пути
val pathFile = File(path)
if (!pathFile.exists() || !pathFile.isDirectory) {
return ScanResult(
isInfected = false,
infectedFiles = emptyList(),
virusNames = emptyMap(),
exitCode = -1,
error = if (isDebug) "Путь не существует или не является директорией: $path" else ""
)
}
println(if (isDebug) " Запуск clamscan (это может занять некоторое время)..." else "")
// Запускаем ClamAV
val processBuilder = ProcessBuilder(
"clamdscan",
"--multiscan",
"--stdout",
"--infected",
path
)
processBuilder.redirectErrorStream(true)
val process = processBuilder.start()
val output = process.inputStream.bufferedReader().readText()
val exitCode = process.waitFor()
// Парсим вывод
output.lines().forEach { line ->
if (line.contains(" FOUND")) {
val colonIndex = line.indexOf(": ")
if (colonIndex > 0) {
val fileName = line.substring(0, colonIndex)
val virusInfo = line.substringAfter(" FOUND").trim()
infectedFiles.add(fileName)
virusNames[fileName] = virusInfo
}
}
}
return ScanResult(
isInfected = infectedFiles.isNotEmpty(),
infectedFiles = infectedFiles,
virusNames = virusNames,
exitCode = exitCode,
error = if (isDebug) (if (exitCode != 0 && exitCode != 1) "ClamAV вернул код ошибки: $exitCode" else "") else ""
)
} catch (e: Exception) {
println(if (isDebug) "Ошибка при выполнении clamscan: ${e.message}" else "")
return ScanResult(
isInfected = false,
infectedFiles = emptyList(),
virusNames = emptyMap(),
exitCode = -1,
error = if (isDebug) e.message else ""
)
}
}
private fun saveScanLog(results: List<ScanResult>) {
try {
val logDir = File("/home/${System.getProperty("user.name")}/usb-scanner-logs")
if (!logDir.exists()) {
logDir.mkdirs()
}
val timestamp = System.currentTimeMillis()
val logFile = File(logDir, "scan_$timestamp.log")
logFile.bufferedWriter().use { writer ->
writer.write("=== USB Virus Scanner Log ===\n")
writer.write("Time: ${java.time.LocalDateTime.now()}\n\n")
results.forEachIndexed { index, result ->
writer.write("Device ${index + 1}:\n")
if (result.isInfected) {
writer.write("Status: INFECTED\n")
writer.write("Found ${result.infectedFiles.size} infected files:\n")
result.infectedFiles.take(20).forEach { file ->
writer.write(" $file: ${result.virusNames[file]}\n")
}
if (result.infectedFiles.size > 20) {
writer.write(" ... and ${result.infectedFiles.size - 20} more files\n")
}
} else if (result.error != null) {
writer.write("Status: ERROR\n")
writer.write("Error: ${result.error}\n")
} else {
writer.write("Status: CLEAN\n")
}
writer.write("\n")
}
}
println(if (isDebug) "📝 Лог сохранён: ${logFile.absolutePath}" else "")
} catch (e: Exception) {
println(if (isDebug) "Ошибка сохранения лога: ${e.message}" else "")
}
}
suspend fun sambaStartSharing() {
try {
currentState = SystemState.SAMBA
var process: Process? = null
val sambaStartServiceCmd = listOf("systemctl", "start", "smbd")
val processBuilder = ProcessBuilder(sambaStartServiceCmd)
.redirectErrorStream(true)
process = withContext(Dispatchers.IO) {
processBuilder.start()
}
} catch (e: Exception) {
e.printStackTrace()
currentState = SystemState.ERROR
delay(10000.milliseconds)
}
}
fun sambaShutdown() {
unmount()
try {
var process: Process? = null
val sambaStopServiceCmd = listOf("systemctl", "stop", "smbd")
val processBuilder = ProcessBuilder(sambaStopServiceCmd)
.redirectErrorStream(true)
process = processBuilder.start()
scanJob?.cancel()
currentState = SystemState.IDLE
} catch (e: Exception) {}
}
fun unmount() {
for (i in 0..9) {
val path = if (i == 0) "/media/usb" else "/media/usb$i"
try {
val processBuilder = ProcessBuilder("umount", "-f", path)
.redirectErrorStream(true)
val process = processBuilder.start()
val exitCode = process.waitFor()
if (exitCode == 0) {
println(if (isDebug) "Успешно размонтировано: $path" else "")
}
} catch (e: Exception) {
println(if (isDebug) "Ошибка при размонтировании $path: ${e.message}" else "")
}
}
}
fun shutdown() {
println(if (isDebug) "\nЗавершение работы..." else "")
isRunning = false
indicationJob?.cancel()
scanJob?.cancel()
scope.cancel()
// Выключаем все светодиоды
try {
if (::greenLed.isInitialized) {
greenLed.setValue(0)
yellowLed.setValue(0)
redLed.setValue(0)
}
} catch (e: Exception) {
// Игнорируем ошибки при закрытии
}
println(if (isDebug) "✅ Система остановлена" else "")
}
}
data class USBDevice(
val device: String,
val mountPoint: String
)
data class ScanResult(
val isInfected: Boolean,
val infectedFiles: List<String>,
val virusNames: Map<String, String>,
val exitCode: Int,
val error: String? = null
)
fun scheduleUpdaterAt3AM() {
val scheduler = Executors.newSingleThreadScheduledExecutor()
val now = Calendar.getInstance()
val targetTime = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 3)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
var initialDelay = targetTime.timeInMillis - now.timeInMillis
// Если время уже прошло сегодня, планируем на завтра
if (initialDelay <= 0) {
targetTime.add(Calendar.DAY_OF_MONTH, 1)
initialDelay = targetTime.timeInMillis - now.timeInMillis
}
scheduler.scheduleAtFixedRate(
{ antivirusDatabaseUpdater() },
initialDelay,
TimeUnit.DAYS.toMillis(1),
TimeUnit.MILLISECONDS
)
println("Планировщик запущен. Первый запуск через ${initialDelay / 1000 / 60} минут")
}
fun antivirusDatabaseUpdater() {
println(if (isDebug) "Обновление базы данных антивируса" else "")
val processBuilder = ProcessBuilder("freshclam", "-vvv")
processBuilder.redirectErrorStream(true)
val process = processBuilder.start()
val reader = BufferedReader(InputStreamReader(process.inputStream))
if (isDebug) {
reader.useLines { lines ->
lines.forEach { println("Process output: $it") }
}
}
process.waitFor()
}
fun main(args: Array<String>) {
if (args.contains("--debug")) {
DebugMode.enableDebugging()
}
scheduleUpdaterAt3AM()
println(if (isDebug) "Зелёный - ${PinConfig.GREEN_LED}" else "")
println(if (isDebug) "Жёлтый - ${PinConfig.YELLOW_LED}" else "")
println(if (isDebug) "Красный - ${PinConfig.RED_LED}" else "")
println(if (isDebug) "Кнопка - ${PinConfig.BUTTON}" else "")
val scanner = USBVirusScanner()
Runtime.getRuntime().addShutdownHook(Thread {
scanner.shutdown()
})
try {
scanner.initialize()
val buttonListener = ButtonListener()
buttonListener.addClickListener { event ->
scanner.startScan()
}
buttonListener.addLongPressListener { event ->
scanner.sambaShutdown()
}
buttonListener.startListening()
println(
if (isDebug) """
═══════════════════════════════════════
USB Virus Scanner готов к работе
═══════════════════════════════════════
📌 Вставьте флешку и нажмите кнопку
📌 Зелёный LED мигает - режим ожидания
📌 Жёлтый LED мигает - идёт проверка
📌 Зелёный LED горит - флешка чиста
📌 Красный LED горит - найдены вирусы
📌 Для выхода нажмите Ctrl+C
""".trimIndent() else ""
)
while (true) {
Thread.sleep(1000)
}
} catch (e: Exception) {
println(if (isDebug) "❌ Критическая ошибка: ${e.message}" else "")
e.printStackTrace()
scanner.shutdown()
exitProcess(1)
}
}