Własna kontrolka SSIS, część 3 – O pobieraniu danych

Wiemy jak w podstawowy sposób odczytać listę plików w katalogu na serwerze. To teraz pobierzmy te pliki. Jeden z kroków kontenera pakietu SSIS generuje plik tekstowy zawierający listę plików do pobrania. Każdy z plików jest podawany w odrębnej linii – potem wczytamy plik, rozbijemy go na linie i w pętli wczytamy każdy z nich po kolei.

Dla przypomnienia – ważne jest, żeby podawać pełne ścieżki do plików względem katalogu głównego. Przeglądając dokumentację WinSCP zauważyłem jeszcze jedną nowość w wersji 5.8 beta: pojawił się parametr string FullName , który zwraca pełną ścieżkę do pliku. Nie trzeba będzie sklejać parametru przesyłanej ścieżki z nazwą pliku odczytaną jako Name. Ponieważ w kontrolce będziemy korzystać z wersji beta biblioteki to warto mieć na uwadze.

Przejdźmy do kodu. Na razie popatrzymy jak to wygląda w Powershell. Podobnie jak poprzednio zaczynamy od parametrów i funkcji, która pobierze plik. Nadal korzystamy z przykładu z dokumentacji.

# fileName             == pełna nazwa (ze ścieżką) pliku z listą plików do pobrania
# destinationDirectory == katalog, do którego ściągamy pliki (bez kończącego slasha)
# maxFiles             == maksymalna ilość plików do ściągnięcia w ramach jednej sesji (żeby serwer nie wyrzucał)

Param (
  [string] $fileName,
  [string] $destinationDirectory,
  [int] $maxFiles
)

function DownloadFile ($session, $filePath, $destinationPath)
{
    $transferResult = $session.GetFiles($filePath, $destinationPath)
    
    # Did the download succeded?
    if (!$transferResult.IsSuccess)
    {
        # Print error (but continue with other files)
        Write-Host $filepath, " NIE OK", $transferResult.Failures[0].Message
    }
    else
    {
        Write-Host $filepath, " OK"
    }
}

Skrypt przyjmuje trzy parametry – co ma pobrać, gdzie zapisać i ile ma plików maksymalnie do pobrania w ramach jednej sesji (żeby serwer dość kategorycznie nie podziękował za pobieranie tylu plików na raz). Pobieranie jest realizowane przez GetFiles, które dla każdego pobranego pliku zwraca informację o statusie pobierania, który możemy zweryfikować. Wynik pobierania zapisujemy sobie na wyjściu, żeby ewentualnie sprawdzić co poszło nie tak.

Uwaga na boku: pobieramy za każdym razem pojedynczy plik, mimo że Session.GetFiles potrafi pobrać wiele plików na raz – jako pierwszy parametr podajemy po prostu maskę plików do pobrania. W naszym przypadku bierzemy jednak pod uwagę, że nazwy plików są dowolne, mogą zawierać różne prefiksy, daty generowania w dowolnej postaci (np. dzień.miesiąc.rok) i wykombinować dość uniwersalną maskę jest zadaniem mocno nietrywialnym. Dla nas maską jest cała nazwa pliku, ale można się zastanowić, żeby kontrolka zamiast predefiniowanej listy plików zwracała jednak coś określonego przez maskę.

try
{
    # Load WinSCP .NET assembly
    #Add-Type -Path "WinSCPnet.dll"
    Add-Type -Path (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "WinSCPnet.dll")
 
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.Protocol = [WinSCP.Protocol]::Ftp
    $sessionOptions.HostName = "adres.serwera.wstaw.tu"
    $sessionOptions.UserName = "a.tu.uzytkownika.nazwe"
    $sessionOptions.Password = "a.tu.haslo.jego"
 
    $session = New-Object WinSCP.Session
    $session.ExecutablePath = (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "WinSCP.exe")
    
    try
    {
        # Connect
        $session.Open($sessionOptions)
        
        # Get files list
        $lines = Get-Content $fileName
        
        try {
            $counter = 1
            
            foreach ($line in $lines)
            {
                if ($line -eq $null) { break }
                
                # reconnect every $maxFiles file (don't hammer the server)
                if (($counter % $maxFiles) -eq 0)
                {
                    $session.Close()
                    $session.Open($sessionOptions)
                    
                    #Write-Host "Rozłączam i podłączam, żeby nie dać się ubić serwerowi"
                }
                
                # Download files
                $destinationPath = $destinationDirectory + "\"  + $line -replace "/",  "_"
                #$line, $destinationPath
                
                DownloadFile $session $line $destinationPath
                
                $counter ++
            }
        }
        finally {
        
        }
        
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }

    Write-Host "Koniec"
    exit 0
}
catch [Exception]
{
    Write-Host $_.Exception.Message
    exit 1
}

W kodzie głównym powtarzamy to, co przy ściąganiu listy plików – parametry połączenia do serwera (rzecz jasna – do optymalizacji), wczytanie biblioteki winscp.dll i podanie lokalizacji pliku winscp.exe. Tak jak poprzednio biblioteka winscp.dll jest zarejestrowana w Global Assembly Cache, ale tutaj pamiętamy, że zastosowaliśmy obejście problemu z jednego z przykładów w dokumentacji. W kodzie kontrolki będzie trzeba uwzględnić możliwość, że biblioteka może być w GAC, ale wcale nie musi.

Pobieranie plików to wywoływanie naszej funkcji DownloadFile dla każdego pliku z listy wczytanej przez Get-Content. Obejście pobierania zbyt dużej liczby plików jest zrealizowane przez rozłączenie się z serwerem i połączenie na nowo po osiągnięciu przekazanego progu. Może nie jest to najbardziej eleganckie rozwiązanie, ale działa. Na koniec sprawdzenie czy wystąpiły jakieś wyjątki i zakończenie pracy.

Patrząc z perspektywy czasu zastanawiam się nad celem zastosowania zagnieżdżonego try w ramach głównej konstrukcji try/catch/finally i przyznam, ze ciężko mi sobie teraz przypomnieć czemu tak. Do zmiany w ramach biblioteki kontrolki, chyba że przypomnę sobie po co to było.

Jedno z pytań, które się nasuwa – dlaczego pobieramy pliki przez generowanie listy, zamiast wykorzystać Session.SynchronizeDirectories. Faktycznie – upraszcza to bardzo mocno całą operację, ale katalog, do którego ściągam pliki wcale nie musi zawierać pełnej kopii plików na serwerze. Obecnie nie potrzebuję rozgraniczania katalogów lokalnych na takie same jak na serwerze, dodatkowo część plików trzymanych lokalnie archiwizuję żeby nie zabierały miejsca – a na źródłowym (S)FTP plików nikt nie rusza. Przy zastosowaniu synchronizacji katalogów pobierałbym za każdym razem pewną część plików, która kompletnie mnie nie interesuje.

Co dalej? Wiemy mniej więcej co chcemy osiągnąć, mamy wykombinowany sposób pobierania plików z serwera i kilka pomysłów, które urodziły się podczas opisywania obecnego modelu. Następny krok to wreszcie rozpoczęcie projektu i wrzucenie pierwszego kodu do repozytorium. Początkowo bez szaleństw – trzeba się zapoznać z Gitem, utworzyć projekt i odpowiednie referencje, sprawdzić co z oprogramowania i bibliotek już mamy, a czego nam potrzeba do szczęścia.

Reklamy

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Zdjęcie na Google+

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s