Загрузка данных


# --- Настройки ---
$ftpHost     = "10.0.2.15"
$ftpPath     = "/AisNalog3"
$ftpUser     = "anonymous"
$ftpPassword = ""

$baseDir = $PSScriptRoot
if (-not $baseDir) { $baseDir = Split-Path -Parent $PSCommandPath }
if (-not $baseDir) { $baseDir = Get-Location }

$localBasePath   = $baseDir
$networkBasePath = "Z:\App"
$flagFile        = Join-Path $networkBasePath "success.flag"

$folderMapping = @{
    "Prom"     = "KPE"
    "PredProm" = "PPK"
    "OE"       = "KOE"
    "OEIZ"     = "KOEIZ"
}

$versionPattern = '^\d+_\d+_\d+_\d+$'

# --- Лог ---
$logFile = Join-Path $baseDir "ftp_pipeline.log"

function Write-PipeLog {
    param([string]$Message, [string]$Level = "INFO")
    $line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] $Message"
    $color = switch ($Level) {
        "ERROR" { "Red" }
        "WARN"  { "Yellow" }
        "OK"    { "Green" }
        "QUEUE" { "Magenta" }
        default { "Gray" }
    }
    Write-Host $line -ForegroundColor $color
    Add-Content -Path $logFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
}

Write-PipeLog "FTP:      $ftpHost$ftpPath"
Write-PipeLog "Локально: $localBasePath"
Write-PipeLog "Сеть:     $networkBasePath"
Write-PipeLog "Лог:      $logFile"

# Удаляем старый флаг
if (Test-Path $flagFile) {
    Write-PipeLog "Удаляю старый success.flag" "WARN"
    Remove-Item $flagFile -Force -ErrorAction SilentlyContinue
}

# Чистим старые .ready и .skipped от прошлых запусков
Get-ChildItem -Path $localBasePath -Filter "*.ready" -File -ErrorAction SilentlyContinue | Remove-Item -Force
Get-ChildItem -Path $localBasePath -Filter "*.skipped" -File -ErrorAction SilentlyContinue | Remove-Item -Force

# JOB 1: FTP WORKER
$ftpJob = Start-Job -Name "FTPWorker" -ScriptBlock {
    param($ftpHost, $ftpPath, $ftpUser, $ftpPassword,
          $localBasePath, $networkBasePath, $folderMapping,
          $versionPattern, $logFile)

    function Write-Log {
        param([string]$Message, [string]$Level = "INFO")
        $line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [FTP] [$Level] $Message"
        Write-Host $line -ForegroundColor $(switch ($Level) {
            "ERROR" { "Red" }; "WARN" { "Yellow" }; "OK" { "Green" }; default { "Gray" }
        })
        Add-Content -Path $logFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
    }

    function Get-FtpFoldersWithDates {
        param([string]$ftpPath)
        $folders = @{}
        try {
            $uri = "ftp://$ftpHost$ftpPath/"
            $request = [System.Net.FtpWebRequest]::Create($uri)
            $request.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
            $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
            $request.UseBinary = $true; $request.UsePassive = $true
            $request.EnableSsl = $false; $request.KeepAlive = $false
            $request.Timeout = 30000
            $response = $request.GetResponse()
            $stream = $response.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($stream)
            $listing = $reader.ReadToEnd()
            $reader.Close(); $response.Close()
            $lines = $listing -split "`r?`n" | Where-Object { $_ -ne "" }
            foreach ($line in $lines) {
                $isDirectory = $false; $folderName = $null; $dateTime = $null
                if ($line -match "^d.*\s+(\S+)$") {
                    $folderName = $matches[1]; $isDirectory = $true
                    if ($line -match "(\w{3})\s+(\d{1,2})\s+(\d{2}:\d{2})") {
                        $month = $matches[1]; $day = $matches[2]; $time = $matches[3]
                        $year = (Get-Date).Year
                        try { $dateTime = [DateTime]::ParseExact("$month $day $year $time", "MMM d yyyy HH:mm", [System.Globalization.CultureInfo]::InvariantCulture) }
                        catch { try { $dateTime = [DateTime]::ParseExact("$month $day $year $time", "MMM dd yyyy HH:mm", [System.Globalization.CultureInfo]::InvariantCulture) } catch { } }
                    }
                } elseif ($line -match "<DIR>\s+(\S+)$") {
                    $folderName = $matches[1]; $isDirectory = $true
                    if ($line -match "(\d{2}-\d{2}-\d{2})\s+(\d{2}:\d{2}[AP]M)") {
                        try { $dateTime = [DateTime]::ParseExact("$($matches[1]) $($matches[2])", "MM-dd-yy hh:mmtt", [System.Globalization.CultureInfo]::InvariantCulture) } catch { }
                    }
                }
                if ($isDirectory -and $folderName -and $folderName -ne "." -and $folderName -ne "..") {
                    if ($dateTime) { $folders[$folderName] = $dateTime } else { $folders[$folderName] = [DateTime]::MinValue }
                }
            }
            return $folders
        } catch { Write-Log "Ошибка списка папок: $_" "ERROR"; return @{} }
    }

    function Get-FtpFileList {
        param([string]$ftpPath)
        $items = @()
        try {
            $uri = "ftp://$ftpHost$ftpPath/"
            $request = [System.Net.FtpWebRequest]::Create($uri)
            $request.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory
            $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
            $request.UseBinary = $true; $request.UsePassive = $true
            $request.EnableSsl = $false; $request.KeepAlive = $false
            $request.Timeout = 30000
            $response = $request.GetResponse()
            $stream = $response.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($stream)
            $listing = $reader.ReadToEnd()
            $reader.Close(); $response.Close()
            $lines = $listing -split "`r?`n" | Where-Object { $_ -ne "" }
            foreach ($line in $lines) { $items += $line.Trim() }
            return $items
        } catch { Write-Log "Ошибка списка файлов: $_" "ERROR"; return @() }
    }

    function Get-FtpFileSize {
        param([string]$remoteUri)
        try {
            $request = [System.Net.FtpWebRequest]::Create($remoteUri)
            $request.Method = [System.Net.WebRequestMethods+Ftp]::GetFileSize
            $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
            $request.UseBinary = $true; $request.UsePassive = $true
            $request.EnableSsl = $false; $request.KeepAlive = $false
            $request.Timeout = 30000
            $response = $request.GetResponse()
            $size = $response.ContentLength
            $response.Close()
            if ($size -gt 0) { return [int64]$size }
        } catch { }
        return -1
    }

    function Download-FtpFile {
        param([string]$remoteUri, [string]$localPath, [string]$fileName)
        try {
            $request = [System.Net.FtpWebRequest]::Create($remoteUri)
            $request.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
            $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
            $request.UseBinary = $true; $request.UsePassive = $true
            $request.EnableSsl = $false; $request.Timeout = 60000
            $response = $request.GetResponse()
            $ftpStream = $response.GetResponseStream()
            $localStream = [System.IO.File]::Create($localPath)
            $bufferSize = 4MB
            $buffer = New-Object Byte[] $bufferSize
            $bytesRead = $ftpStream.Read($buffer, 0, $bufferSize)
            $downloaded = 0
            while ($bytesRead -gt 0) {
                $localStream.Write($buffer, 0, $bytesRead)
                $downloaded += $bytesRead
                $bytesRead = $ftpStream.Read($buffer, 0, $bufferSize)
            }
            $localStream.Close(); $ftpStream.Close(); $response.Close()
            $mb = [math]::Round($downloaded / 1MB, 2)
            Write-Log "Скачан: $fileName ($mb MB)" "OK"
            return $true
        } catch {
            Write-Log "Ошибка скачивания $fileName : $_" "ERROR"
            if (Test-Path $localPath) { Remove-Item $localPath -Force -ErrorAction SilentlyContinue }
            return $false
        }
    }

    Write-Log "Worker запущен"

    foreach ($remoteFolder in $folderMapping.Keys) {
        $localFolder = $folderMapping[$remoteFolder]
        $remoteBasePath = "$ftpPath/$remoteFolder"
        $networkFolder = Join-Path $networkBasePath $localFolder

        Write-Log "Обработка: $remoteFolder -> $localFolder"

        if (!(Test-Path $networkFolder)) {
            try { New-Item -ItemType Directory -Path $networkFolder -Force -ErrorAction Stop | Out-Null }
            catch { Write-Log "Не удалось создать $networkFolder : $_" "ERROR"; continue }
        }

        $foldersWithDates = Get-FtpFoldersWithDates -ftpPath $remoteBasePath
        if ($foldersWithDates.Count -eq 0) { Write-Log "$remoteFolder : нет подпапок" "ERROR"; continue }

        $latestFolder = $null; $latestDate = [DateTime]::MinValue
        foreach ($folder in $foldersWithDates.Keys) {
            $date = $foldersWithDates[$folder]
            if ($date -gt $latestDate) { $latestDate = $date; $latestFolder = $folder }
        }
        if (-not $latestFolder) { Write-Log "$remoteFolder : не определена самая новая папка" "ERROR"; continue }

        # Проверка версии
        $versionFile = $null
        Get-ChildItem -Path $networkFolder -File -Force -ErrorAction SilentlyContinue | ForEach-Object {
            if ($_.BaseName -match $versionPattern) { $versionFile = $_ }
        }

        if ($versionFile -and $versionFile.BaseName -eq $latestFolder) {
            Write-Log "$remoteFolder : версия актуальна ($latestFolder). Пропускаем." "OK"
            "Skipped" | Out-File (Join-Path $localBasePath "$localFolder.skipped") -Encoding UTF8 -Force
            continue
        }

        # Ищем RAR
        $ekpPath = "$remoteBasePath/$latestFolder/EKP"
        $ekpFiles = Get-FtpFileList -ftpPath $ekpPath
        if ($ekpFiles.Count -eq 0) { Write-Log "$remoteFolder : EKP пуста" "ERROR"; continue }

        $archives = @()
        foreach ($file in $ekpFiles) { if ($file -match "\.rar$") { $archives += $file } }
        if ($archives.Count -eq 0) { Write-Log "$remoteFolder : нет RAR" "ERROR"; continue }

        $archiveName = $archives[0]
        $networkFilePath = Join-Path $networkFolder $archiveName

        # Уже на сети?
        if (Test-Path $networkFilePath) {
            $ftpUri = "ftp://$ftpHost$ekpPath/$archiveName"
            $ftpSize = Get-FtpFileSize -remoteUri $ftpUri
            $netSize = (Get-Item $networkFilePath).Length
            if ($ftpSize -gt 0 -and $ftpSize -eq $netSize) {
                Write-Log "$remoteFolder : файл уже полностью на сети. Пропускаем." "OK"
                "Skipped" | Out-File (Join-Path $localBasePath "$localFolder.skipped") -Encoding UTF8 -Force
                continue
            } else {
                Write-Log "$remoteFolder : файл на сети неполный. Перекачаем." "WARN"
                Remove-Item $networkFilePath -Force -ErrorAction SilentlyContinue
            }
        }

        # Скачиваем локально
        $localFilePath = Join-Path $localBasePath $archiveName
        if (Test-Path $localFilePath) { Remove-Item $localFilePath -Force -ErrorAction SilentlyContinue }
        $downloadUri = "ftp://$ftpHost$ekpPath/$archiveName"

        if (-not (Download-FtpFile -remoteUri $downloadUri -localPath $localFilePath -fileName $archiveName)) {
            Write-Log "$remoteFolder : ошибка скачивания" "ERROR"
            continue
        }

        # Создаём .ready маркер → SMB Worker увидит и начнёт копировать
        $readyMarker = "$localFilePath.ready"
        @{
            LocalFolder  = $localFolder
            LatestFolder = $latestFolder
            ArchiveName  = $archiveName
            NetworkFolder = $networkFolder
        } | ConvertTo-Json | Out-File $readyMarker -Encoding UTF8 -Force

        Write-Log "[QUEUE] Готов к копированию: $archiveName ($localFolder)" "OK"
    }

    Write-Log "Worker завершён"
} -ArgumentList $ftpHost, $ftpPath, $ftpUser, $ftpPassword,
                 $localBasePath, $networkBasePath, $folderMapping,
                 $versionPattern, $logFile

# JOB 2: SMB WORKER
$smbJob = Start-Job -Name "SMBWorker" -ScriptBlock {
    param($localBasePath, $versionPattern, $logFile)

    function Write-Log {
        param([string]$Message, [string]$Level = "INFO")
        $line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [SMB] [$Level] $Message"
        Write-Host $line -ForegroundColor $(switch ($Level) {
            "ERROR" { "Red" }; "WARN" { "Yellow" }; "OK" { "Green" }; default { "Gray" }
        })
        Add-Content -Path $logFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
    }

    function Copy-FileWithProgress {
        param([string]$src, [string]$dst, [string]$fileName)

        try {
            $sourceStream = [System.IO.File]::OpenRead($src)
            $destStream = [System.IO.File]::Create($dst)
            $bufferSize = 4MB
            $buffer = New-Object Byte[] $bufferSize
            $total = $sourceStream.Length
            $copied = 0
            $lastPercent = -1

            try {
                while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferSize)) -gt 0) {
                    $destStream.Write($buffer, 0, $bytesRead)
                    $copied += $bytesRead
                    $percent = [math]::Floor(($copied / $total) * 100)
                    if ($percent -ne $lastPercent) {
                        $lastPercent = $percent
                        $mb = [math]::Round($copied / 1MB, 1)
                        $mbTotal = [math]::Round($total / 1MB, 1)
                        $filled = [math]::Floor(30 * $percent / 100)
                        $bar = ("*" * $filled) + ("." * (30 - $filled))
                        Write-Host "`r  [SMB] $fileName : [$bar] $percent% ($mb/$mbTotal MB)" -NoNewline -ForegroundColor Cyan
                    }
                }
            } finally {
                $sourceStream.Close()
                $destStream.Close()
            }

            Write-Host ""
            Write-Log "Скопирован: $fileName" "OK"
            return $true
        } catch {
            Write-Host ""
            Write-Log "Ошибка копирования $fileName : $_" "ERROR"
            return $false
        }
    }

    function Update-VersionFile {
        param([string]$folderPath, [string]$latestFolder)
        try {
            $versionFile = $null
            Get-ChildItem -Path $folderPath -File -Force -ErrorAction SilentlyContinue | ForEach-Object {
                if ($_.BaseName -match $versionPattern) { $versionFile = $_ }
            }
            if ($versionFile) {
                if ($versionFile.BaseName -ne $latestFolder) {
                    $ext = $versionFile.Extension
                    if ([string]::IsNullOrEmpty($ext)) { $ext = ".txt" }
                    Rename-Item -Path $versionFile.FullName -NewName "$latestFolder$ext" -Force
                    Write-Log "Маркер версии обновлён: $latestFolder$ext" "OK"
                }
            } else {
                New-Item -ItemType File -Path (Join-Path $folderPath "$latestFolder.txt") -Force | Out-Null
                Write-Log "Маркер версии создан: $latestFolder.txt" "OK"
            }
        } catch {
            Write-Log "Ошибка обновления маркера: $_" "ERROR"
        }
    }

    Write-Log "Worker запущен. Слежу за *.ready"

    $processedFiles = @{}
    $ftpDoneMarker = Join-Path $localBasePath "ftp.done"

    while ($true) {
        # Проверяем, закончил ли FTP worker
        $ftpDone = Test-Path $ftpDoneMarker

        # Ищем .ready файлы
        $readyFiles = Get-ChildItem -Path $localBasePath -Filter "*.ready" -File -ErrorAction SilentlyContinue

        if ($readyFiles.Count -eq 0) {
            if ($ftpDone) {
                Write-Log "FTP worker завершён, и .ready больше нет. Выходим."
                break
            }
            Start-Sleep -Seconds 1
            continue
        }

        foreach ($readyFile in $readyFiles) {
            if ($processedFiles.ContainsKey($readyFile.FullName)) { continue }

            Write-Log "Найден готовый файл: $($readyFile.Name)"

            try {
                $meta = Get-Content $readyFile.FullName -Raw -Encoding UTF8 | ConvertFrom-Json
            } catch {
                Write-Log "Не удалось прочитать $($readyFile.FullName): $_" "ERROR"
                $processedFiles[$readyFile.FullName] = $true
                Remove-Item $readyFile.FullName -Force -ErrorAction SilentlyContinue
                continue
            }

            $localRar = $readyFile.FullName -replace '\.ready$', ''
            if (-not (Test-Path $localRar)) {
                Write-Log "Локальный файл исчез: $localRar" "ERROR"
                $processedFiles[$readyFile.FullName] = $true
                Remove-Item $readyFile.FullName -Force -ErrorAction SilentlyContinue
                continue
            }

            $destPath = Join-Path $meta.NetworkFolder $meta.ArchiveName

            if (Copy-FileWithProgress -src $localRar -dst $destPath -fileName $meta.ArchiveName) {
                # Удаляем локальный .rar и .ready
                Remove-Item $localRar -Force -ErrorAction SilentlyContinue
                Remove-Item $readyFile.FullName -Force -ErrorAction SilentlyContinue
                Write-Log "Удалён локальный: $localRar"

                # Обновляем маркер версии на сети
                Update-VersionFile -folderPath $meta.NetworkFolder -latestFolder $meta.LatestFolder
            }

            $processedFiles[$readyFile.FullName] = $true
        }

        if ($ftpDone -and (Get-ChildItem -Path $localBasePath -Filter "*.ready" -File -ErrorAction SilentlyContinue).Count -eq 0) {
            break
        }

        Start-Sleep -Milliseconds 500
    }

    Write-Log "Worker завершён"
} -ArgumentList $localBasePath, $versionPattern, $logFile

# ОЖИДАНИЕ ЗАВЕРШЕНИЯ
Write-PipeLog "Запущены job'ы: FTP=$($ftpJob.Id), SMB=$($smbJob.Id)"
Write-PipeLog "Ожидаем завершения FTP worker'а..."

# Ждём FTP job
Wait-Job -Job $ftpJob | Out-Null
Write-PipeLog "FTP worker завершён. Сигналим SMB worker'у."

# Создаём маркер "ftp.done" → SMB worker поймёт, что больше нечего ждать
"SMB может завершаться" | Out-File (Join-Path $localBasePath "ftp.done") -Encoding UTF8 -Force

# Ждём SMB job
Wait-Job -Job $smbJob | Out-Null
Write-PipeLog "SMB worker завершён."

# Показываем вывод job'ов
Write-PipeLog "=== ВЫВОД FTP WORKER ==="
Receive-Job -Job $ftpJob | ForEach-Object { Write-Host $_ }
Write-PipeLog "=== ВЫВОД SMB WORKER ==="
Receive-Job -Job $smbJob | ForEach-Object { Write-Host $_ }

# Удаляем job'ы
Remove-Job -Job $ftpJob -Force
Remove-Job -Job $smbJob -Force

# ПРОВЕРКА РЕЗУЛЬТАТА
Write-PipeLog "========================================"
Write-PipeLog "ПРОВЕРКА РЕЗУЛЬТАТА"

$allOk = $true

foreach ($remoteFolder in $folderMapping.Keys) {
    $localFolder = $folderMapping[$remoteFolder]
    $networkFolder = Join-Path $networkBasePath $localFolder
    $skippedMarker = Join-Path $localBasePath "$localFolder.skipped"

    $hasRar = $false
    if (Test-Path $networkFolder) {
        $rarFiles = Get-ChildItem -Path $networkFolder -Filter "*.rar" -File -ErrorAction SilentlyContinue
        $hasRar = $rarFiles.Count -gt 0
    }

    $wasSkipped = Test-Path $skippedMarker

    if ($hasRar -or $wasSkipped) {
        $status = if ($wasSkipped) { "УСПЕШНО (пропущено)" } else { "УСПЕШНО (скопировано)" }
        Write-PipeLog "  $remoteFolder -> $localFolder : $status" "OK"
    } else {
        Write-PipeLog "  $remoteFolder -> $localFolder : ОШИБКА" "ERROR"
        $allOk = $false
    }
}

# Чистим временные маркеры
Get-ChildItem -Path $localBasePath -Filter "*.skipped" -File -ErrorAction SilentlyContinue | Remove-Item -Force
Remove-Item (Join-Path $localBasePath "ftp.done") -Force -ErrorAction SilentlyContinue

# СОЗДАНИЕ success.flag
if ($allOk) {
    try {
        if (!(Test-Path $networkBasePath)) {
            New-Item -ItemType Directory -Path $networkBasePath -Force | Out-Null
        }

        $flagContent = "Конвейер завершён: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`r`n"
        $flagContent += "Все папки обработаны успешно.`r`n"

        $flagContent | Out-File -FilePath $flagFile -Encoding UTF8
        Write-PipeLog "Файл success.flag создан: $flagFile" "OK"
    } catch {
        Write-PipeLog "Ошибка создания success.flag: $_" "ERROR"
    }
} else {
    Write-PipeLog "Не все папки обработаны. success.flag НЕ создан." "ERROR"
}

Write-PipeLog "========================================"
Write-PipeLog "Скрипт завершён."


Read-Host "123"
exit 0