- Changed window size to mobile phone format (400x800) - Removed width condition for ActiveProxyFooter - now always visible - Added run-umbrix.sh launch script with icon copying - Stats cards now display on all screen sizes
130 lines
4.6 KiB
Dart
130 lines
4.6 KiB
Dart
import 'dart:io';
|
||
import 'package:fpdart/fpdart.dart';
|
||
import 'package:umbrix/core/utils/exception_handler.dart';
|
||
import 'package:umbrix/features/log/data/log_parser.dart';
|
||
import 'package:umbrix/features/log/data/log_path_resolver.dart';
|
||
import 'package:umbrix/features/log/model/log_entity.dart';
|
||
import 'package:umbrix/features/log/model/log_failure.dart';
|
||
import 'package:umbrix/singbox/service/singbox_service.dart';
|
||
import 'package:umbrix/utils/custom_loggers.dart';
|
||
|
||
abstract interface class LogRepository {
|
||
TaskEither<LogFailure, Unit> init();
|
||
Stream<Either<LogFailure, List<LogEntity>>> watchLogs();
|
||
TaskEither<LogFailure, Unit> clearLogs();
|
||
}
|
||
|
||
class LogRepositoryImpl with ExceptionHandler, InfraLogger implements LogRepository {
|
||
LogRepositoryImpl({
|
||
required this.singbox,
|
||
required this.logPathResolver,
|
||
});
|
||
|
||
final SingboxService singbox;
|
||
final LogPathResolver logPathResolver;
|
||
|
||
// Ограничения на размер файлов логов
|
||
static const int _maxLogFileSize = 5 * 1024 * 1024; // 5 МБ
|
||
static const int _maxBackupFiles = 2; // Храним только 2 backup файла
|
||
|
||
@override
|
||
TaskEither<LogFailure, Unit> init() {
|
||
return exceptionHandler(
|
||
() async {
|
||
if (!await logPathResolver.directory.exists()) {
|
||
await logPathResolver.directory.create(recursive: true);
|
||
}
|
||
|
||
// Инициализация core логов с ротацией
|
||
await _initLogFileWithRotation(logPathResolver.coreFile());
|
||
|
||
// Инициализация app логов с ротацией
|
||
await _initLogFileWithRotation(logPathResolver.appFile());
|
||
|
||
return right(unit);
|
||
},
|
||
LogUnexpectedFailure.new,
|
||
);
|
||
}
|
||
|
||
/// Инициализация файла логов с автоматической ротацией
|
||
Future<void> _initLogFileWithRotation(File logFile) async {
|
||
try {
|
||
if (await logFile.exists()) {
|
||
final fileSize = await logFile.length();
|
||
|
||
// Если файл превышает лимит - делаем ротацию
|
||
if (fileSize > _maxLogFileSize) {
|
||
loggy.info('Log file too large: ${fileSize / 1024 / 1024}MB, rotating...');
|
||
await _rotateLogFile(logFile);
|
||
} else {
|
||
// Просто очищаем если размер нормальный
|
||
await logFile.writeAsString("");
|
||
}
|
||
} else {
|
||
await logFile.create(recursive: true);
|
||
}
|
||
} catch (e, st) {
|
||
loggy.warning('Error initializing log file: $e', e, st);
|
||
// В случае ошибки просто создаём новый файл
|
||
await logFile.create(recursive: true);
|
||
}
|
||
}
|
||
|
||
/// Ротация файла логов (создаём backup и очищаем текущий)
|
||
Future<void> _rotateLogFile(File logFile) async {
|
||
try {
|
||
final basePath = logFile.path;
|
||
|
||
// Удаляем самый старый backup если есть
|
||
for (int i = _maxBackupFiles; i > 0; i--) {
|
||
final oldBackup = File('$basePath.$i');
|
||
if (i == _maxBackupFiles && await oldBackup.exists()) {
|
||
await oldBackup.delete();
|
||
loggy.debug('Deleted old backup: $basePath.$i');
|
||
}
|
||
|
||
// Переименовываем backups (example.log.1 → example.log.2)
|
||
if (i > 1) {
|
||
final prevBackup = File('$basePath.${i - 1}');
|
||
if (await prevBackup.exists()) {
|
||
await prevBackup.rename('$basePath.$i');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Текущий файл → backup.1
|
||
if (await logFile.exists()) {
|
||
await logFile.rename('$basePath.1');
|
||
loggy.debug('Rotated log file to: $basePath.1');
|
||
}
|
||
|
||
// Создаём новый пустой файл
|
||
await logFile.create();
|
||
loggy.info('Log file rotation completed successfully');
|
||
} catch (e, st) {
|
||
loggy.warning('Error rotating log file: $e', e, st);
|
||
// В случае ошибки просто перезаписываем файл
|
||
await logFile.writeAsString("");
|
||
}
|
||
}
|
||
|
||
@override
|
||
Stream<Either<LogFailure, List<LogEntity>>> watchLogs() {
|
||
return singbox.watchLogs(logPathResolver.coreFile().path).map((event) => event.map(LogParser.parseSingbox).toList()).handleExceptions(
|
||
(error, stackTrace) {
|
||
loggy.warning("error watching logs", error, stackTrace);
|
||
return LogFailure.unexpected(error, stackTrace);
|
||
},
|
||
);
|
||
}
|
||
|
||
@override
|
||
TaskEither<LogFailure, Unit> clearLogs() {
|
||
return exceptionHandler(
|
||
() => singbox.clearLogs().mapLeft(LogFailure.unexpected).run(),
|
||
LogFailure.unexpected,
|
||
);
|
||
}
|
||
}
|