Код рабочий, осталось фиксить взаимодействие с ClamAV

This commit is contained in:
2026-04-03 08:31:28 +05:00
committed by Thiflict
parent 7a611ebbb8
commit 31503d8b68
3 changed files with 84 additions and 126 deletions

View File

@@ -1,7 +1,7 @@
package ru.thiflict package ru.thiflict
import kotlinx.coroutines.* import kotlinx.coroutines.*
import ru.thiflict.DebugMode.isDebug import ru.thiflict.DebugMode.DEBUG
import ru.thiflict.PinConfig.BUTTON import ru.thiflict.PinConfig.BUTTON
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
@@ -26,6 +26,10 @@ class ButtonListener(
clickListeners.add(listener) clickListeners.add(listener)
} }
fun removeClickListener(listener: ButtonClickListener) {
clickListeners.remove(listener)
}
private fun notifyClick() { private fun notifyClick() {
val event = ButtonClickEvent() val event = ButtonClickEvent()
clickListeners.forEach { it.invoke(event) } clickListeners.forEach { it.invoke(event) }
@@ -51,25 +55,25 @@ class ButtonListener(
var line: String? var line: String?
while (reader.readLine().also { line = it } != null) { while (reader.readLine().also { line = it } != null) {
println(if (isDebug) "$line" else "") println(if (DEBUG) "$line" else "")
when { when {
line?.contains("rising") == true -> { line?.contains("rising") == true -> {
currentButtonState.set(true) currentButtonState.set(true)
onStateChange?.invoke(true) onStateChange?.invoke(true)
println(if (isDebug) "Кнопка нажата" else "") println(if (DEBUG) "Кнопка нажата" else "")
// Генерируем событие клика при нажатии // Генерируем событие клика при нажатии
notifyClick() notifyClick()
} }
line?.contains("falling") == true -> { line?.contains("falling") == true -> {
currentButtonState.set(false) currentButtonState.set(false)
onStateChange?.invoke(false) onStateChange?.invoke(false)
println(if (isDebug) "Кнопка разжата" else "") println(if (DEBUG) "Кнопка разжата" else "")
// Убрана проверка на разжатие для генерации события // Убрана проверка на разжатие для генерации события
} }
} }
} }
val exitCode = process!!.waitFor() val exitCode = process!!.waitFor()
println(if (isDebug) "Процесс завершён ($exitCode)" else "") println("Процесс завершён ($exitCode)")
isListening.set(false) isListening.set(false)
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -78,4 +82,10 @@ class ButtonListener(
} }
return currentButtonState.get() return currentButtonState.get()
} }
fun stopListening() {
listenerJob?.cancel()
process?.destroy()
isListening.set(false)
}
} }

View File

@@ -1,9 +1,16 @@
package ru.thiflict package ru.thiflict
import ru.thiflict.DebugMode.isDebug import kotlinx.coroutines.delay
import ru.thiflict.PinConfig
import java.io.File
import kotlin.system.exitProcess
class GPIOController(private val pin: Int) { class GPIOController(private val pin: Int) {
fun setDirection(direction: String) {
// Для libgpiod направление задаётся автоматически
}
fun setValue(value: Int) { fun setValue(value: Int) {
try { try {
// Пробуем разные варианты синтаксиса gpioset // Пробуем разные варианты синтаксиса gpioset
@@ -20,7 +27,7 @@ class GPIOController(private val pin: Int) {
} }
} catch (e: Exception) { } catch (e: Exception) {
println(if (isDebug) "Ошибка установки GPIO $pin = $value: ${e.message}" else "") println("Ошибка установки GPIO $pin = $value: ${e.message}")
} }
} }
@@ -34,7 +41,7 @@ class GPIOController(private val pin: Int) {
process.waitFor() process.waitFor()
output.toIntOrNull() ?: 0 output.toIntOrNull() ?: 0
} catch (e: Exception) { } catch (e: Exception) {
println(if (isDebug) "Ошибка чтения GPIO $pin: ${e.message}" else "") println("Ошибка чтения GPIO $pin: ${e.message}")
0 0
} }
} }
@@ -45,3 +52,4 @@ class GPIOController(private val pin: Int) {
} }

View File

@@ -1,13 +1,9 @@
package ru.thiflict package ru.thiflict
import kotlinx.coroutines.* import kotlinx.coroutines.*
import ru.thiflict.DebugMode.isDebug import ru.thiflict.DebugMode.DEBUG
import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.InputStreamReader
import kotlin.system.exitProcess import kotlin.system.exitProcess
import java.util.*
import java.util.concurrent.*
// Конфигурация GPIO (BCM номера) // Конфигурация GPIO (BCM номера)
object PinConfig { object PinConfig {
@@ -18,11 +14,7 @@ object PinConfig {
} }
object DebugMode { object DebugMode {
var isDebug = false const val DEBUG = true
fun enableDebugging() {
isDebug = true
}
} }
// Состояния системы // Состояния системы
@@ -40,11 +32,12 @@ class USBVirusScanner {
private lateinit var greenLed: GPIOController private lateinit var greenLed: GPIOController
private lateinit var yellowLed: GPIOController private lateinit var yellowLed: GPIOController
private lateinit var redLed: GPIOController private lateinit var redLed: GPIOController
private lateinit var buttonReader: ButtonListener
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun initialize() { fun initialize() {
println(if (isDebug) "Инициализация..." else "") println("Инициализация...")
try { try {
// Проверяем доступность gpiod // Проверяем доступность gpiod
@@ -61,18 +54,16 @@ class USBVirusScanner {
// Запускаем индикацию состояния // Запускаем индикацию состояния
startStateIndication() startStateIndication()
currentState = SystemState.IDLE
Thread.sleep(500)
antivirusDatabaseUpdater() println("Инициализация завершена.")
Thread.sleep(1000)
println(if (isDebug) "Инициализация завершена." else "")
currentState = SystemState.IDLE currentState = SystemState.IDLE
} catch (e: Exception) { } catch (e: Exception) {
currentState = SystemState.ERROR currentState = SystemState.ERROR
println(if (isDebug) "❌ Ошибка инициализации: ${e.message}" else "") println("❌ Ошибка инициализации: ${e.message}")
e.printStackTrace() e.printStackTrace()
throw e throw e
} }
@@ -84,23 +75,23 @@ class USBVirusScanner {
val exitCode = process.waitFor() val exitCode = process.waitFor()
if (exitCode != 0) { if (exitCode != 0) {
throw Exception(if (isDebug) "gpiodetect не найден" else "") throw Exception("gpiodetect не найден")
} }
val output = process.inputStream.bufferedReader().readText() val output = process.inputStream.bufferedReader().readText()
println(if (isDebug) "✅ libgpiod найден" else "") println("✅ libgpiod найден")
if (output.isNotBlank()) { if (output.isNotBlank()) {
println(if (isDebug) "Доступные чипы: ${output.trim()}" else "") println("Доступные чипы: ${output.trim()}")
} }
} catch (e: Exception) { } catch (e: Exception) {
throw Exception(if (isDebug) "libgpiod Не установлен!" else "") throw Exception("libgpiod Не установлен!")
} }
} }
fun buttonPress(defaultButtonState: Boolean): Boolean { fun buttonPress(defaultButtonState: Boolean): Boolean {
var finalButtonState = defaultButtonState var finalButtonState = defaultButtonState
println(if (isDebug) "Начало finalButtonState: $finalButtonState" else "") println(if (DEBUG) "Начало finalButtonState: $finalButtonState" else "")
val buttonListener = ButtonListener() val buttonListener = ButtonListener()
val initialState = buttonListener.startListening { isPressed -> val initialState = buttonListener.startListening { isPressed ->
@@ -108,7 +99,7 @@ class USBVirusScanner {
finalButtonState = true finalButtonState = true
} }
} }
println(if (isDebug) "finalButtonState: $finalButtonState" else "") println(if (DEBUG) "finalButtonState: $finalButtonState" else "")
return finalButtonState return finalButtonState
} }
@@ -173,27 +164,27 @@ class USBVirusScanner {
fun startScan() { fun startScan() {
if (scanJob?.isActive == true) { if (scanJob?.isActive == true) {
println(if (isDebug) "Сканирование уже выполняется" else "") println("Сканирование уже выполняется")
return return
} }
scanJob = scope.launch { scanJob = scope.launch {
currentState = SystemState.SCANNING currentState = SystemState.SCANNING
println(if (isDebug) "=== Начало сканирования ===" else "") println("=== Начало сканирования ===")
try { try {
// Поиск USB-устройств // Поиск USB-устройств
val usbDevices = findUSBDevices() val usbDevices = findUSBDevices()
if (usbDevices.isEmpty()) { if (usbDevices.isEmpty()) {
println(if (isDebug) "Устройства не найдены" else "") println("Устройства не найдены")
currentState = SystemState.ERROR currentState = SystemState.ERROR
delay(5000) delay(5000)
currentState = SystemState.IDLE currentState = SystemState.IDLE
return@launch return@launch
} }
println(if (isDebug) "📀 Найдены USB-устройства:" else "") println("📀 Найдены USB-устройства:")
usbDevices.forEach { device -> usbDevices.forEach { device ->
println(" - ${device.mountPoint}") println(" - ${device.mountPoint}")
} }
@@ -203,23 +194,23 @@ class USBVirusScanner {
val scanResults = mutableListOf<ScanResult>() val scanResults = mutableListOf<ScanResult>()
for (device in usbDevices) { for (device in usbDevices) {
println(if (isDebug) "🔍 Сканирование: ${device.mountPoint}" else "") println("🔍 Сканирование: ${device.mountPoint}")
val result = scanWithClamAV(device.mountPoint) val result = scanWithClamAV(device.mountPoint)
scanResults.add(result) scanResults.add(result)
if (result.isInfected) { if (result.isInfected) {
hasVirus = true hasVirus = true
println(if (isDebug) "⚠️ ВНИМАНИЕ! Найдены угрозы!" else "") println("⚠️ ВНИМАНИЕ! Найдены угрозы!")
result.infectedFiles.take(10).forEach { file -> result.infectedFiles.take(10).forEach { file ->
println(" 🦠 ${file.take(100)}") println(" 🦠 ${file.take(100)}")
} }
if (result.infectedFiles.size > 10) { if (result.infectedFiles.size > 10) {
println(if (isDebug) " ... и ещё ${result.infectedFiles.size - 10} файлов" else "") println(" ... и ещё ${result.infectedFiles.size - 10} файлов")
} }
} else if (result.error != null) { } else if (result.error != null) {
println(if (isDebug) "❌ Ошибка сканирования: ${result.error}" else "") println("❌ Ошибка сканирования: ${result.error}")
} else { } else {
println(if (isDebug) "✅ Сканирование завершено: угроз не найдено" else "") println("✅ Сканирование завершено: угроз не найдено")
} }
println() println()
} }
@@ -234,25 +225,25 @@ class USBVirusScanner {
} }
val message = if (hasVirus) { val message = if (hasVirus) {
if (isDebug) "🦠 Найдены вирусы! Красный LED горит" else "" "🦠 Найдены вирусы! Красный LED горит"
} else { } else {
if (isDebug) "✅ Флешка чиста! Зелёный LED горит" else "" "✅ Флешка чиста! Зелёный LED горит"
} }
println(if (isDebug) message else "") println(message)
// Держим состояние 5 секунд // Держим состояние 5 секунд
delay(5000) delay(5000)
currentState = SystemState.IDLE currentState = SystemState.IDLE
} catch (e: Exception) { } catch (e: Exception) {
println(if (isDebug) "❌ Критическая ошибка: ${e.message}" else "") println("❌ Критическая ошибка: ${e.message}")
e.printStackTrace() e.printStackTrace()
currentState = SystemState.ERROR currentState = SystemState.ERROR
delay(5000) delay(5000)
currentState = SystemState.IDLE currentState = SystemState.IDLE
} }
println(if (isDebug) "=== Сканирование завершено ===\n" else "") println("=== Сканирование завершено ===\n")
} }
} }
@@ -293,7 +284,7 @@ class USBVirusScanner {
if (parts.size >= 2) { if (parts.size >= 2) {
val mountPoint = parts[1] val mountPoint = parts[1]
if ((mountPoint.startsWith("/media")) && if ((mountPoint.startsWith("/media") || mountPoint.startsWith("/mnt")) &&
File(mountPoint).exists() && File(mountPoint).exists() &&
devices.none { it.mountPoint == mountPoint }) { devices.none { it.mountPoint == mountPoint }) {
@@ -304,7 +295,7 @@ class USBVirusScanner {
} }
} catch (e: Exception) { } catch (e: Exception) {
println(if (isDebug) "Ошибка при поиске USB-устройств: ${e.message}" else "") println("Ошибка при поиске USB-устройств: ${e.message}")
} }
return devices.distinctBy { it.mountPoint } return devices.distinctBy { it.mountPoint }
@@ -325,7 +316,7 @@ class USBVirusScanner {
infectedFiles = emptyList(), infectedFiles = emptyList(),
virusNames = emptyMap(), virusNames = emptyMap(),
exitCode = -1, exitCode = -1,
error = if (isDebug) "ClamAV не установлен. Установите: sudo apt install clamav" else "" error = "ClamAV не установлен. Установите: sudo apt install clamav"
) )
} }
@@ -337,11 +328,11 @@ class USBVirusScanner {
infectedFiles = emptyList(), infectedFiles = emptyList(),
virusNames = emptyMap(), virusNames = emptyMap(),
exitCode = -1, exitCode = -1,
error = if (isDebug)"Путь не существует или не является директорией: $path" else "" error = "Путь не существует или не является директорией: $path"
) )
} }
println(if (isDebug) " Запуск clamscan (это может занять некоторое время)..." else "") println(" Запуск clamscan (это может занять некоторое время)...")
// Запускаем ClamAV // Запускаем ClamAV
val processBuilder = ProcessBuilder( val processBuilder = ProcessBuilder(
@@ -349,8 +340,8 @@ class USBVirusScanner {
"-r", "-r",
"--stdout", "--stdout",
"--infected", "--infected",
"--max-filesize=1M", "--max-filesize=1G",
"--max-scansize=10M", "--max-scansize=1G",
path path
) )
@@ -378,17 +369,17 @@ class USBVirusScanner {
infectedFiles = infectedFiles, infectedFiles = infectedFiles,
virusNames = virusNames, virusNames = virusNames,
exitCode = exitCode, exitCode = exitCode,
error = if (isDebug) (if (exitCode != 0 && exitCode != 1) "ClamAV вернул код ошибки: $exitCode" else "") else "" error = if (exitCode != 0 && exitCode != 1) "ClamAV вернул код ошибки: $exitCode" else null
) )
} catch (e: Exception) { } catch (e: Exception) {
println(if (isDebug) " Ошибка при выполнении clamscan: ${e.message}" else "") println(" Ошибка при выполнении clamscan: ${e.message}")
return ScanResult( return ScanResult(
isInfected = false, isInfected = false,
infectedFiles = emptyList(), infectedFiles = emptyList(),
virusNames = emptyMap(), virusNames = emptyMap(),
exitCode = -1, exitCode = -1,
error = if (isDebug) e.message else "" error = e.message
) )
} }
} }
@@ -428,14 +419,14 @@ class USBVirusScanner {
} }
} }
println(if (isDebug) "📝 Лог сохранён: ${logFile.absolutePath}" else "") println("📝 Лог сохранён: ${logFile.absolutePath}")
} catch (e: Exception) { } catch (e: Exception) {
println(if (isDebug) "Ошибка сохранения лога: ${e.message}" else "") println("Ошибка сохранения лога: ${e.message}")
} }
} }
fun shutdown() { fun shutdown() {
println(if (isDebug) "\nЗавершение работы..." else "") println("\nЗавершение работы...")
isRunning = false isRunning = false
indicationJob?.cancel() indicationJob?.cancel()
@@ -454,7 +445,7 @@ class USBVirusScanner {
// Игнорируем ошибки при закрытии // Игнорируем ошибки при закрытии
} }
println(if (isDebug) "✅ Система остановлена" else "") println("✅ Система остановлена")
} }
} }
@@ -471,63 +462,14 @@ data class ScanResult(
val error: String? = null 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 fun main() {
// Если время уже прошло сегодня, планируем на завтра println(if (DebugMode.DEBUG) "Зелёный - ${PinConfig.GREEN_LED}" else "")
if (initialDelay <= 0) { println(if (DebugMode.DEBUG) "Жёлтый - ${PinConfig.YELLOW_LED}" else "")
targetTime.add(Calendar.DAY_OF_MONTH, 1) println(if (DebugMode.DEBUG) "Красный - ${PinConfig.RED_LED}" else "")
initialDelay = targetTime.timeInMillis - now.timeInMillis println(if (DebugMode.DEBUG) "Кнопка - ${PinConfig.BUTTON}" else "")
}
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 defaultButtonState = false val defaultButtonState = false
val scanner = USBVirusScanner() val scanner = USBVirusScanner()
@@ -545,23 +487,21 @@ fun main(args: Array<String>) {
} }
buttonListener.startListening() buttonListener.startListening()
println(if (isDebug) """ println("═══════════════════════════════════════")
═══════════════════════════════════════ println(" USB Virus Scanner готов к работе")
USB Virus Scanner готов к работе println("═══════════════════════════════════════")
═══════════════════════════════════════ println("📌 Вставьте флешку и нажмите кнопку")
📌 Вставьте флешку и нажмите кнопку println("📌 Зелёный LED мигает - режим ожидания")
📌 Зелёный LED мигает - режим ожидания println("📌 Жёлтый LED мигает - идёт проверка")
📌 Жёлтый LED мигает - идёт проверка println("📌 Зелёный LED горит - флешка чиста")
📌 Зелёный LED горит - флешка чиста println("📌 Красный LED горит - найдены вирусы")
📌 Красный LED горит - найдены вирусы println("📌 Для выхода нажмите Ctrl+C\n")
📌 Для выхода нажмите Ctrl+C
""".trimIndent() else "")
while (true) { while (true) {
Thread.sleep(1000) Thread.sleep(1000)
} }
} catch (e: Exception) { } catch (e: Exception) {
println(if (isDebug) "❌ Критическая ошибка: ${e.message}" else "") println("❌ Критическая ошибка: ${e.message}")
e.printStackTrace() e.printStackTrace()
scanner.shutdown() scanner.shutdown()
exitProcess(1) exitProcess(1)