- Добавлен release-body.md для подробных заметок о релизе на русском языке. - Реализован release-gitea.ps1 для автоматизированного релиза Gitea с помощью PowerShell. - Создан release-gitea.sh для автоматизированного релиза Gitea с помощью Bash. - Добавлен release.sh для сборки и маркировки релизов с поддержкой нескольких платформ. - Улучшен пользовательский интерфейс благодаря информативному логированию и обработке ошибок. - Добавлена поддержка переменных окружения и управления конфигурацией. - Добавлена функция создания архивов и загрузки ресурсов в Gitea.
270 lines
7.5 KiB
Go
270 lines
7.5 KiB
Go
package entities
|
||
|
||
import "time"
|
||
|
||
// Config представляет конфигурацию приложения
|
||
type Config struct {
|
||
Scanner ScannerConfig `yaml:"scanner"`
|
||
Compression AppCompressionConfig `yaml:"compression"`
|
||
Processing ProcessingConfig `yaml:"processing"`
|
||
Output OutputConfig `yaml:"output"`
|
||
}
|
||
|
||
// ScannerConfig настройки сканирования директорий
|
||
type ScannerConfig struct {
|
||
SourceDirectory string `yaml:"source_directory"`
|
||
TargetDirectory string `yaml:"target_directory"`
|
||
ReplaceOriginal bool `yaml:"replace_original"`
|
||
}
|
||
|
||
// AppCompressionConfig настройки сжатия приложения
|
||
type AppCompressionConfig struct {
|
||
Level int `yaml:"level"`
|
||
Algorithm string `yaml:"algorithm"`
|
||
AutoStart bool `yaml:"auto_start"`
|
||
UniPDFLicenseKey string `yaml:"unipdf_license_key"`
|
||
// Настройки сжатия изображений
|
||
EnableJPEG bool `yaml:"enable_jpeg"`
|
||
EnablePNG bool `yaml:"enable_png"`
|
||
JPEGQuality int `yaml:"jpeg_quality"` // Качество JPEG в процентах (10-50)
|
||
PNGQuality int `yaml:"png_quality"` // Качество PNG в процентах (10-50)
|
||
}
|
||
|
||
// ProcessingConfig настройки обработки
|
||
type ProcessingConfig struct {
|
||
ParallelWorkers int `yaml:"parallel_workers"`
|
||
TimeoutSeconds int `yaml:"timeout_seconds"`
|
||
RetryAttempts int `yaml:"retry_attempts"`
|
||
}
|
||
|
||
// OutputConfig настройки вывода
|
||
type OutputConfig struct {
|
||
LogLevel string `yaml:"log_level"`
|
||
ProgressBar bool `yaml:"progress_bar"`
|
||
LogToFile bool `yaml:"log_to_file"`
|
||
LogFileName string `yaml:"log_file_name"`
|
||
LogMaxSizeMB int `yaml:"log_max_size_mb"`
|
||
}
|
||
|
||
// ProcessingStatus статус обработки
|
||
type ProcessingStatus struct {
|
||
// Текущая фаза обработки
|
||
Phase ProcessingPhase
|
||
|
||
// Информация о текущем файле
|
||
CurrentFile string
|
||
CurrentFileSize int64
|
||
|
||
// Общая статистика
|
||
TotalFiles int
|
||
ProcessedFiles int
|
||
SuccessfulFiles int
|
||
FailedFiles int
|
||
SkippedFiles int
|
||
|
||
// Прогресс
|
||
Progress float64
|
||
|
||
// Статистика сжатия
|
||
TotalOriginalSize int64
|
||
TotalCompressedSize int64
|
||
TotalSavedSpace int64
|
||
AverageCompression float64
|
||
|
||
// Текущий результат
|
||
LastResult *CompressionResult
|
||
|
||
// Время выполнения
|
||
StartTime time.Time
|
||
ElapsedTime time.Duration
|
||
EstimatedTime time.Duration
|
||
|
||
// Состояние
|
||
IsComplete bool
|
||
Error error
|
||
|
||
// Сообщение для UI
|
||
Message string
|
||
}
|
||
|
||
// ProcessingPhase фаза обработки
|
||
type ProcessingPhase int
|
||
|
||
const (
|
||
PhaseInitializing ProcessingPhase = iota
|
||
PhaseScanning
|
||
PhaseCompressing
|
||
PhaseReplacing
|
||
PhaseCompleted
|
||
PhaseFailed
|
||
)
|
||
|
||
// UIScreen типы экранов UI
|
||
type UIScreen int
|
||
|
||
const (
|
||
UIScreenMenu UIScreen = iota
|
||
UIScreenConfig
|
||
UIScreenProcessing
|
||
// UIScreenResults
|
||
)
|
||
|
||
// Validate проверяет корректность конфигурации приложения
|
||
func (c *AppCompressionConfig) Validate() error {
|
||
// Проверка уровня сжатия
|
||
if c.Level < 10 || c.Level > 90 {
|
||
return ErrInvalidCompressionLevel
|
||
}
|
||
|
||
// Проверка качества JPEG
|
||
if c.EnableJPEG {
|
||
if c.JPEGQuality < 10 || c.JPEGQuality > 50 || c.JPEGQuality%5 != 0 {
|
||
return ErrInvalidJPEGQuality
|
||
}
|
||
}
|
||
|
||
// Проверка качества PNG
|
||
if c.EnablePNG {
|
||
if c.PNGQuality < 10 || c.PNGQuality > 50 || c.PNGQuality%5 != 0 {
|
||
return ErrInvalidPNGQuality
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetSupportedImageFormats возвращает список поддерживаемых форматов изображений
|
||
func (c *AppCompressionConfig) GetSupportedImageFormats() []string {
|
||
var formats []string
|
||
if c.EnableJPEG {
|
||
formats = append(formats, "JPEG")
|
||
}
|
||
if c.EnablePNG {
|
||
formats = append(formats, "PNG")
|
||
}
|
||
return formats
|
||
}
|
||
|
||
// NewProcessingStatus создает новый статус обработки
|
||
func NewProcessingStatus(totalFiles int) *ProcessingStatus {
|
||
return &ProcessingStatus{
|
||
Phase: PhaseInitializing,
|
||
TotalFiles: totalFiles,
|
||
StartTime: time.Now(),
|
||
}
|
||
}
|
||
|
||
// UpdateProgress обновляет прогресс обработки
|
||
func (ps *ProcessingStatus) UpdateProgress() {
|
||
if ps.TotalFiles > 0 {
|
||
ps.Progress = float64(ps.ProcessedFiles) / float64(ps.TotalFiles) * 100
|
||
}
|
||
|
||
ps.ElapsedTime = time.Since(ps.StartTime)
|
||
|
||
// Оценка оставшегося времени
|
||
if ps.ProcessedFiles > 0 && ps.ProcessedFiles < ps.TotalFiles {
|
||
avgTimePerFile := ps.ElapsedTime / time.Duration(ps.ProcessedFiles)
|
||
remainingFiles := ps.TotalFiles - ps.ProcessedFiles
|
||
ps.EstimatedTime = avgTimePerFile * time.Duration(remainingFiles)
|
||
}
|
||
}
|
||
|
||
// AddResult добавляет результат обработки файла
|
||
func (ps *ProcessingStatus) AddResult(result *CompressionResult) {
|
||
ps.ProcessedFiles++
|
||
ps.LastResult = result
|
||
|
||
if result.Success && result.Error == nil {
|
||
ps.SuccessfulFiles++
|
||
ps.TotalOriginalSize += result.OriginalSize
|
||
ps.TotalCompressedSize += result.CompressedSize
|
||
ps.TotalSavedSpace += result.SavedSpace
|
||
|
||
// Пересчитываем среднее сжатие
|
||
if ps.TotalOriginalSize > 0 {
|
||
ps.AverageCompression = ((float64(ps.TotalOriginalSize) - float64(ps.TotalCompressedSize)) / float64(ps.TotalOriginalSize)) * 100
|
||
}
|
||
} else {
|
||
ps.FailedFiles++
|
||
}
|
||
|
||
ps.UpdateProgress()
|
||
}
|
||
|
||
// SetPhase устанавливает фазу обработки
|
||
func (ps *ProcessingStatus) SetPhase(phase ProcessingPhase, message string) {
|
||
ps.Phase = phase
|
||
ps.Message = message
|
||
}
|
||
|
||
// SetCurrentFile устанавлиет текущий обрабатываемый файл
|
||
func (ps *ProcessingStatus) SetCurrentFile(filePath string, size int64) {
|
||
ps.CurrentFile = filePath
|
||
ps.CurrentFileSize = size
|
||
}
|
||
|
||
// Complete завершает обработку
|
||
func (ps *ProcessingStatus) Complete() {
|
||
ps.IsComplete = true
|
||
ps.Phase = PhaseCompleted
|
||
ps.Progress = 100
|
||
ps.ElapsedTime = time.Since(ps.StartTime)
|
||
ps.EstimatedTime = 0
|
||
}
|
||
|
||
// Fail отмечает обработку как неудачную
|
||
func (ps *ProcessingStatus) Fail(err error) {
|
||
ps.IsComplete = true
|
||
ps.Phase = PhaseFailed
|
||
ps.Error = err
|
||
ps.ElapsedTime = time.Since(ps.StartTime)
|
||
}
|
||
|
||
// GetPhaseName возвращает название фазы
|
||
func (phase ProcessingPhase) String() string {
|
||
switch phase {
|
||
case PhaseInitializing:
|
||
return "Инициализация"
|
||
case PhaseScanning:
|
||
return "Сканирование файлов"
|
||
case PhaseCompressing:
|
||
return "Сжатие файлов"
|
||
case PhaseReplacing:
|
||
return "Замена оригиналов"
|
||
case PhaseCompleted:
|
||
return "Завершено"
|
||
case PhaseFailed:
|
||
return "Ошибка"
|
||
default:
|
||
return "Неизвестно"
|
||
}
|
||
}
|
||
|
||
// FormatElapsedTime форматирует время выполнения
|
||
func (ps *ProcessingStatus) FormatElapsedTime() string {
|
||
duration := ps.ElapsedTime
|
||
if duration < time.Second {
|
||
return "< 1 сек"
|
||
}
|
||
if duration < time.Minute {
|
||
return duration.Round(time.Second).String()
|
||
}
|
||
return duration.Round(time.Second).String()
|
||
}
|
||
|
||
// FormatEstimatedTime форматирует оставшееся время
|
||
func (ps *ProcessingStatus) FormatEstimatedTime() string {
|
||
if ps.EstimatedTime == 0 {
|
||
return "N/A"
|
||
}
|
||
duration := ps.EstimatedTime
|
||
if duration < time.Second {
|
||
return "< 1 сек"
|
||
}
|
||
if duration < time.Minute {
|
||
return duration.Round(time.Second).String()
|
||
}
|
||
return duration.Round(time.Second).String()
|
||
}
|