Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Inne artykuły

[C++, POCO] Operacje na plikach ZIP przy użyciu biblioteki POCO

[artykuł] Artykuł opisuje w jaki sposób można obsługiwać pliki ZIP za pomocą biblioteki POCO z poziomu języka C++.

O czym będzie następujący artykuł

Zdarza się, że z poziomu programu chcemy z paru plików zrobić jeden lub chcemy, aby nasze pliki zajmowały mniej (np. na potrzeby ich transferu przez sieć), lub np. aby nasz program aktualizował moduły ze spakowanych plików. Bywa też, że pewne formaty to tak naprawdę pliki zip, z tym że pod inną nazwą m.in. https://pl.wikipedia.org/wiki​/Office_Open_XML. W tym artykule operuję się na formacie https://pl.wikipedia.org/wiki​/ZIP, gdyż jest on jednym z najpopularniejszych formatów kompresji bezstratnej. W tym artykule opiszę operowanie na plikach ZIP przy użyciu biblioteki POCO Zip.

W niniejszym artykule znajdują się przykłady operowania na plikach ZIP m.in. tworzenie archiwum ZIP, rozpakowywanie, a także manipulowanie już istniejącym archiwum:
  • przeszukiwanie
  • dodawanie kolejnych plików
  • usuwanie plików i katalogów z archiwum
  • zmiana nazw plików w archiwum
  • podmiana plików w archiwum

Instalacja biblioteki

W zasadzie do użytku bibliotekę można pozyskać i zainstalować tak jak opisałem w  moim artykule na temat pobierania poczty przy pomocy biblioteki Poco. Z tą różnicą, że tym razem potrzebna jest nam edycja pełna (Complete Edition). Niestety ta edycja wymaga zainstalowanych OpenSSL, Crypto, MySQL Client i ODBC, więc jeśli ich nie posiadamy ani nie potrzebujemy to dobrze jest wykluczyć ich kompilowanie i instalowanie. Do uruchomienia przykładów z tego artykułu powinny nam wystarczyć następujące komponenty:
  • Foundation
  • Zip
  • Net (jeśli chcemy pobierać paczki przez HTTP/FTP)

Budowanie na Windows

W pliku components wystarczy zostawić jedynie:

Foundation
Net
Zip
Cała reszta tak samo jak w podlinkowanym artykule.

Budowanie na Linuxie

Aby zbudować tylko to co potrzebne można użyć dostarczonego skryptu configure w następujący sposób:

./configure --omit=XML,JSON,Util,NetSSL_OpenSSL,Data,Data/SQLite,Data/ODBC,Data/MySQL,MongoDB,PageCompiler,PageCompiler/File2Page,Crypto,PDF,CppParser
Lub jak ktoś chce mieć podobnie wyrafinowany build jak ja (tylko statycznie, bez testów i bibliotek dynamicznych):

./configure --static --no-tests --no-sharedlibs --prefix=/sciezka/gdzie/skompilowac/biblioteke --omit=XML,JSON,Util,NetSSL_OpenSSL,Data,Data/SQLite,Data/ODBC,Data/MySQL,MongoDB,PageCompiler,PageCompiler/File2Page,Crypto,PDF,CppParser
cała reszta wg podlinkowanego artykułu.

Kompilujemy z następującymi flagami:
-lPocoZip -lPocoFoundation -pthread
, natomiast jeśli ktoś budował w niedomyślnej ścieżce musi dodać jeszcze adres bibliotek:
-L/sciezka/gdzie/skompilowac/biblioteke/lib
 przed powyższymi -l*, oraz ścieżki do includów
-isystem/sciezka/gdzie/skompilowac/biblioteke/include
.

Operacje na plikach ZIP dostarczone przez Poco::Zip

Poniżej zawarte są przykłady użytkowania wraz z opisem. Wiele tych przykładów było zainspirowanych dokumentacją biblioteki POCO ZIP:
https://pocoproject.org/docs​/Poco.Zip.html
https://pocoproject.org/docs​/ZipUserGuide.html

Tworzenie archiwum

Używane funkcje i obiekty

W celu tworzenia archiwum używamy obiektu Poco::Zio::Compress:
C/C++
Compress(
std::ostream & out,
bool seekableOut
);
Drugi argument specyfikuje jak zapisywać pliki zip, ustawianie na true jest zalecane dla plików lokalnych (to daje mniejszy plik ZIP), kompresja bezpośrednio do sieci wymaga ustawienia flagi na false. Dlatego też w tym artykule będę ustawiał tą flagę na prawdziwą.

Do pakowania używamy następujących funkcji, poniżej są zacytowane funkcje z dokumentacji:
C/C++
void Compress::addFile(
const Poco::Path & file,
const Poco::Path & fileName,
Poco::Zip::ZipCommon::CompressionMethod cm = Poco::Zip::ZipCommon::CM_DEFLATE,
Poco::Zip::ZipCommon::CompressionLevel cl = Poco::Zip::ZipCommon::CL_MAXIMUM
);

void Compress::addDirectory(
const Poco::Path & entryName,
const Poco::DateTime & lastModifiedAt
);

void Compress::addRecursive(
const Poco::Path & entry,
Poco::Zip::ZipCommon::CompressionLevel cl = Poco::Zip::ZipCommon::CL_MAXIMUM,
bool excludeRoot = true,
const Poco::Path & name = Poco::Path()
);

void Compress::addRecursive(
const Poco::Path & entry,
Poco::Zip::ZipCommon::CompressionMethod cm,
Poco::Zip::ZipCommon::CompressionLevel cl = Poco::Zip::ZipCommon::CL_MAXIMUM,
bool excludeRoot = true,
const Poco::Path & name = Poco::Path()
);

Poco::Zip::ZipArchive Compress::close(); // aby plik był gotowy musimy go zamknąć

Przykład kompresji jednego pliku

Zacznijmy od najprostrzego przykładu -dodania do archiwum jednego pliku:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Zip/Compress.h>

int main()
{
    const char * outputZipFileName = "paczka.zip";
    std::ofstream outputZipArchive( outputZipFileName, std::ios::binary );
   
    Poco::Zip::Compress compressor( outputZipArchive, true );
   
    const char * fileToZip = "plik.txt";
    compressor.addFile( fileToZip, fileToZip );
   
    compressor.close();
    outputZipArchive.close();
}
// g++ -isystem/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/include c1.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/lib -lPocoZip -lPocoFoundation -pthread -o c1
W dokumentacji zalecają używanie std::ofstream zamiast std::fstream, argumentując, że to pierwsze jest mniej podatne na błędy. Musimy również używać trybu binarnego.
Powyższe rozwiązanie wygląda podejrzanie, a dokładnie:
C/C++
c.addFile( fileToZip, fileToZip );
problem w tym, że pierwszy argument to ścieżka do pliku, drugi to ścieżka do tegoż pliku w archiwum.
Uwaga: funkcja addFile() służy do dodawania plików, a nie katalogów!

Przykład kompresji wielu plików

Teraz czas na dodawanie wielu plików, podanych jako argumenty uruchomienia programu, dodałem również obsługę błędów (np. jeśli podamy katalog):
C/C++
#include <iostream>
#include <fstream>
#include <Poco/Zip/Compress.h>

int main( int argc, char * argv[] )
{
    if( argc < 2 )
    {
        std::cerr << "Usage: " << argv[ 0 ] << " files_to_compress+\n";
        return - 1;
    }
    const char * outputZipFileName = "paczka.zip";
    std::ofstream outputZipArchive( outputZipFileName, std::ios::binary );
   
    Poco::Zip::Compress c( outputZipArchive, true );
   
    for( unsigned i = 1; i < argc; ++i )
    {
        const char * fileToZip = argv[ i ];
        std::cout << i << ") Adding: " << fileToZip << std::endl;
       
        try
        {
            c.addFile( fileToZip, fileToZip );
        }
        catch( const Poco::FileNotFoundException & e )
        {
            std::cerr << "Can not add file " << fileToZip << ", because: " << e.what() << std::endl;
        }
    }
   
    c.close();
    outputZipArchive.close();
}

// g++ -isystem/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/include c2.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/lib -lPocoZip -lPocoFoundation -pthread -o c2
przykładowy output:

$ ./c2 zdjecie.jpg inneZdjecie/zdjecie2.jpg doSpakowania/aa.csv Main.cc
1) Adding: zdjecie.jpg
2) Adding: inneZdjecie/zdjecie2.jpg
3) Adding: doSpakowania/aa.csv
4) Adding: Main.cc

Powyższy przykład umieszcza pliki w archiwum o takiej samej ścieżce jak w rzeczywistości, jeśli jednak chcemy wrzucać pliki bezpośrednio musimy wyciągnąć nazwę pliku, tak się składa, że biblioteka Poco zawiera Poco::Path udostępniające funkcje do manipulacji plikami i katalogami, dając możliwość wyjęcia samej nazwy pliku:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Zip/Compress.h>
#include <Poco/File.h>
#include <Poco/Path.h>

int main( int argc, char * argv[] )
{
    if( argc < 2 )
    {
        std::cerr << "Usage: " << argv[ 0 ] << " files_to_compress+\n";
        return - 1;
    }
    const char * outputZipFileName = "paczka.zip";
    std::ofstream outputZipArchive( outputZipFileName, std::ios::binary );
   
    Poco::Zip::Compress compressor( outputZipArchive, true );
   
    for( unsigned i = 1; i < argc; ++i )
    {
        Poco::Path pathToFile( argv[ i ] );
       
        if( pathToFile.isDirectory() )
             std::cerr << i << ") The path " << pathToFile.toString() << " points to directory: IGNORING\n";
        else if( pathToFile.isFile() )
        {
            try
            {
                std::cout << i << ") Adding: " << pathToFile.toString() << " as " << pathToFile.getFileName() << std::endl;
                compressor.addFile( pathToFile, pathToFile.getFileName() );
            }
            catch( const Poco::FileNotFoundException & e )
            {
                std::cerr << i << "Can not add file " << pathToFile.toString() << ", because: " << e.what() << std::endl;
            }
        }
    }
   
    compressor.close();
    outputZipArchive.close();
}

// g++ -isystem/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/include c3.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/lib -lPocoZip -lPocoFoundation -pthread -o c3
output przykładowy:

$ ./c3 zdjecie.jpg inneZdjecie/zdjecie2.jpg doSpakowania/aa.csv Main.cc
1) Adding: zdjecie.jpg as zdjecie.jpg
2) Adding: inneZdjecie/zdjecie2.jpg as zdjecie2.jpg
3) Adding: doSpakowania/aa.csv as aa.csv
4) Adding: Main.cc as Main.cc

Powyższe rozwiązanie ma taką wadę, że sprawdzenie czy coś jest katalogiem odbywa się tylko po nazwie, czyli np. /home/grzegorz wg powyższego sprawdzenia nie jest katalogiem, natomiast /home/grzegorz/ już jest, dlatego poniżej użyję pomocniczej klasy Poco::File, dzięki temu będę mógł pozbyć się try/catcha, dodatkowo dodam obsługę różnych poziomów i algorytmów kompresji, poniżej funkcja-gotowiec:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/File.h>
#include <Poco/Path.h>
#include <Poco/Zip/Compress.h>
#include <Poco/Zip/ZipCommon.h>

void compress( const char * outputFileName, int numberOfFilesToCompress, char * filesToCompress[], Poco::Zip::ZipCommon::CompressionLevel cl = Poco::Zip::ZipCommon::CL_MAXIMUM, Poco::Zip::ZipCommon::CompressionMethod cm = ZipCommon::CM_DEFLATE )
{
    std::ofstream outputZipArchive( outputFileName, std::ios::binary );
    if( !outputZipArchive )
    {
        std::cerr << "Problem with creating archive" << outputFileName << std::endl;
        return;
    }
   
    Poco::Zip::Compress compressor( outputZipArchive, true );
   
    for( unsigned i = 0; i < numberOfFilesToCompress; ++i )
    {
        Poco::File myFile( filesToCompress[ i ] );
        Poco::Path pathToFile( myFile.path() );
       
        if( myFile.isDirectory() )
             std::cerr << i << ") The path " << pathToFile.toString() << " points to directory: IGNORING\n";
        else
        {
            std::cout << i << ") Adding: " << pathToFile.toString() << " as " << pathToFile.getFileName() << std::endl;
            compressor.addFile( pathToFile, pathToFile.getFileName(), cm, cl );
        }
    }
   
    compressor.close();
    outputZipArchive.close();
}

Mamy dostępne następujące poziomy kompresji:
  • CL_NORMAL = 0
  • CL_MAXIMUM = 1
  • CL_FAST = 2
  • CL_SUPERFAST = 3
jeśli chodzi o algorytmy kompresji odsyłam do dokumentacji, na tej samej stronie są też poziomy kompresji.

Dodawanie katalogów

Mamy możliwość dodać bez problemu pusty katalog, który nie musi nawet istnieć:
C/C++
c.addDirectory( "Katalog/", Poco::DateTime() );
proszę zwrócić uwagę, że musimy dać separator na końcu nazwy, gdyż inaczej zostanie wyrzucony wyjątek, a to dlatego, że walidacja czy katalog odbywa się po nazwie a nie po rzeczywistym pliku, dlatego dodawany katalog nie musi istnieć. Musimy również podać datę ostatniej modyfikacji katalogu, jeśli on nie istnieje możemy podać tak jak wyżej, w przeciwnym razie możemy pobrać datę np. w taki sposób:
C/C++
compressor.addDirectory( "katalog/", Poco::File( "katalog/" ).getLastModified() );

Rekursywne dodawanie

Dodawanie pojedynczych plików to jedno, natomiast dobrze jest mieć jeszcze możliwość rekursywnego dodawania plików (i również taką właśnie funkcjonalność dostarcza nam biblioteka).
Chwilę temu pokazałem jaką postać mają funkcje do dodawania rekursywnego, aby dodać katalog rekursywnie wystarczy zawołać:
C/C++
Poco::Zip::Compress compressor(...);
compressor.addRecursive( directoryToCompress );
możemy też użyć dodatkowych argumentów takich jak poziom i metoda kompresji oraz flagę czy wykluczyć katalog-korzeń.

Działanie funkcji addRecursive() jest następujące:
mamy katalog:

pliki i katalogi
  katalog
    plik1
to jeśli zawołamy:
C/C++
compressor.addRecursive( "katalog" );
to otrzymamy strukturę:

plik1
jeśli natomiast zawołamy:
C/C++
compressor.addRecursive( "katalog", Poco::ZipCommon::CL_MAXIMUM, false );
to otrzymamy:

pliki i katalogi
    plik1
na szczęście funkcje addRecursive() mają jako ostatni parametr możliwość wyspecjalizowania do jakiej ścieżki zostanie wrzucona rekursywnie zawartość katalogu.
Poniżej przykład podsumowujący dodawanie plików i rekursywne dodawanie katalogów:
C/C++
#include <iostream>
#include <fstream>
#include <cstring>

#include <Poco/File.h>
#include <Poco/Path.h>
#include <Poco/Zip/Compress.h>
#include <Poco/Zip/ZipCommon.h>

void compressFull( const char * outputFileName, int numberOfFilesToCompress, char * filesToCompress[], Poco::Zip::ZipCommon::CompressionLevel cl = Poco::Zip::ZipCommon::CL_MAXIMUM, Poco::Zip::ZipCommon::CompressionMethod cm = Poco::Zip::ZipCommon::CM_DEFLATE )
{
    std::ofstream outputZipArchive( outputFileName, std::ios::binary );
    if( !outputZipArchive )
    {
        std::cerr << "Problem with creating archive" << outputFileName << std::endl;
        return;
    }
   
    Poco::Zip::Compress compressor( outputZipArchive, true );
   
    for( unsigned i = 0; i < numberOfFilesToCompress; ++i )
    {
        if( 0 == strlen( filesToCompress[ i ] ) )
        {
            std::cerr << "Empty file names are not allowed!\n";
            continue;
        }
       
        Poco::File myFile( filesToCompress[ i ] );
        Poco::Path pathToFile( myFile.path() );
       
        if( myFile.isDirectory() )
        {
            std::cout << i << ") Adding directory recursivery: " << pathToFile.toString() << " as " << pathToFile.getFileName() << std::endl;
            const bool excludeRoot = true;
            compressor.addRecursive( pathToFile, cm, cl, excludeRoot, pathToFile.getFileName() );
        }
        else
        {
            std::cout << i << ") Adding file: " << pathToFile.toString() << " as " << pathToFile.getFileName() << std::endl;
            compressor.addFile( pathToFile, pathToFile.getFileName(), cm, cl );
        }
    }
   
    compressor.close();
    outputZipArchive.close();
}

int main( int argc, char * argv[] )
{
    if( argc < 2 )
    {
        std::cerr << "Usage: " << argv[ 0 ] << " files_to_compress+\n";
        return - 1;
    }
   
    compressFull( "paczka.zip", argc - 1, argv + 1 );
}

// g++ -isystem/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/include c5.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/lib -lPocoZip -lPocoFoundation -pthread -o c5

A oto output:

$  ./c5 plik.txt plik2.cc zdjecia /home/grzegorz/pliki ''
0) Adding file: plik.txt as plik.txt
1) Adding file: plik2.cc as plik2.cc
2) Adding directory recursivery: zdjecia as zdjecia
3) Adding directory recursivery: /home/grzegorz/pliki as pliki
Empty file names are not allowed!

Komentarz w archiwum

Mamy również możliwość odczytania/zapisania komentarza do archiwum (komentarz w archiwum)
C/C++
const std::string & Compress::getZipComment() const;
void Compress::setZipComment(
const std::string & comment
);
Więcej informacji na temat tworzenia archiwum https://pocoproject.org/docs​/Poco.Zip.Compress.html.

Rozpakowywanie archiwum ZIP

Funkcjonalnością analogiczną do pakowania jest rozpakowywanie.

Obiekty i funkcje służące do rozpakowywania

Odbywa się przez obiekt Poco::Zip::Decompress:
C/C++
Poco::Zip::Decompress(
std::istream & in,
const Poco::Path & outputDir, // to musi być katalogiem, jeśli nie istnieje zostanie utworzony (nierekursywnie)
bool flattenDirs = false, // jeśli to będzie prawdziwe wtedy wszystkie pliki zostaną rozpakowane bezpośrednio w katalogu outputDir, bez drzewa katalogów
bool keepIncompleteFiles = false
);
Oraz przy użyciu metody:
C/C++
Decompress::decompressAllFiles();

Proste rozpakowywanie całego archiwum

Więc weźmy najprostszy przykład rozpakowywania całego archiwum:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Zip/Decompress.h>

void decompressSimple( const char * zipFileName, const char * outputDirectory )
{
    std::ifstream in( zipFileName );
    if( !in )
    {
        std::cerr << "File " << zipFileName << " can't be opened!\n";
        return;
    }
   
    Poco::Zip::Decompress decompressor( in, outputDirectory );
    decompressor.decompressAllFiles();
}

Rozpakowywanie całego archiwum z wylistowaniem rozpakowanych elementów

Oczywiście jeśli rozpakowujemy archiwum to chcemy wiedzieć, jakie pliki zostały rozpakowane. Biblioteka daje nam również możliwość wyboru czy chcemy wszystkie pliki rozpakować bezpośrednio we wskazanym katalogu, czy chcemy odtworzenia całej struktury katalogowej, mamy również możliwość wyboru czy chcemy zachować niekompletne pliki, czy je usunąć, oto kolejna funkcja-gotowiec:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Zip/Decompress.h>

void decompressFile( const char * archiveFileName, const char * outputDirectory, bool ignoreDirectoryTree = false, bool keepIncompleteFiles = false )
{
    std::ifstream in( archiveFileName );
    if( !in )
    {
        std::cerr << "File " << archiveFileName << " can't be opened!" << std::endl;
        return;
    }
   
    Poco::Zip::Decompress decompressor( in, outputDirectory, ignoreDirectoryTree, keepIncompleteFiles );
    decompressor.decompressAllFiles();
   
    typedef std::map < std::string, Poco::Path > ZipMapping;
    const ZipMapping & zm = decompressor.mapping();
    for( ZipMapping::const_iterator it = zm.begin(); it != zm.end(); ++it )
         std::cout << "Extracted: " << it->first << " -> " << it->second.toString() << std::endl;
   
}
a oto nasz przykładowy output, jak widać jeśli ignorujemy drzewo katalogów w razie powtarzających się nazw pliki te zostaną nadpisane:

Extracted: katalog1/aaa.jpg -> aaa.jpg
Extracted: katalog1/bbb.jpg -> bbb.jpg
Extracted: katalog1/katalog2/aaa.jpg -> aaa.jpg
Extracted: katalog1/katalog2/bbb.jpg -> bbb.jpg
Extracted: katalog1/katalog2/ccc.jpg -> ccc.jpg
Extracted: katalog1/katalog2/katalog3/polskie znaki.txt -> polskie znaki.txt

Sygnalizacja błędu/sukcesu podczas rozpakowywania

Mamy jeszcze możliwość podpięcia funkcji obsługujących każdy sukces i błąd rozpakowywania archiwum. Taka możliwość może być szczególnie przydatna w środowisku wielowątkowym. W poniższym przykładzie do rozpakowywania dwóch archiwów używam dwóch wątków:
C/C++
#include <iostream>
#include <fstream>
#include <thread>
#include <chrono>

#include <Poco/Zip/Decompress.h>
#include <Poco/Delegate.h>

class Decompressor
{
public:
    void decompressFile( const char * archiveFileName, const char * outputDirectory = ".", bool ignoreDirectoryTree = false, bool keepIncompleteFiles = false )
    {
        std::ifstream in( archiveFileName );
        if( !in )
        {
            std::cerr << "File " << archiveFileName << " can't be opened!" << std::endl;
            return;
        }
       
        Poco::Zip::Decompress decompressor( in, outputDirectory, ignoreDirectoryTree, keepIncompleteFiles );
       
        const auto errorHandler = Poco::Delegate < Decompressor, std::pair < const Poco::Zip::ZipLocalFileHeader, const std::string > >( this, & Decompressor::onDecompressError );
        const auto successHandler = Poco::Delegate < Decompressor, std::pair < const Poco::Zip::ZipLocalFileHeader, const Poco::Path > >( this, & Decompressor::onDecompressOK );
       
        decompressor.EError += errorHandler;
        decompressor.EOk += successHandler;
       
        decompressor.decompressAllFiles();
       
        decompressor.EError -= errorHandler;
        decompressor.EOk -= successHandler;
    }
   
    void onDecompressError( const void * pSender, std::pair < const Poco::Zip::ZipLocalFileHeader, const std::string >& info )
    {
        std::cerr << "Error during unpacking: " << info.second << '\n';
    }
   
    void onDecompressOK( const void * pSender, std::pair < const Poco::Zip::ZipLocalFileHeader, const Poco::Path >& info )
    {
        std::cout << "Unpacking OK: " << info.second.toString() << '\n';
    }
};

void decompressFile( const char * archiveFileName )
{
    Decompressor decompressor;
    decompressor.decompressFile( archiveFileName );
}

int main()
{
    std::thread firstUnpacker( decompressFile, "archiwum1.zip" );
    std::thread secondUnpacker( decompressFile, "archiwum2.zip" );
   
    std::cout << "continuing work...\n";
   
    std::this_thread::sleep_for( std::chrono::seconds( 4 ) );
   
    firstUnpacker.join();
    secondUnpacker.join();
   
    std::cout << "unpacking done.\n";
}

// g++ --std=c++11 -isystem/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/include d2.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/lib -lPocoZip -lPocoFoundation -pthread && ./a.out
Funkcje do obsługi dobrze jest odpiąć po skończonym rozpakowywaniu. Możemy podpiąć wiele funkcji do obsługi błędów i sukcesu.

Rozpakowywanie bezpośrednio z internetu

Kolejną możliwością rozpakowywania jest rozpakowywanie bezpośrednio z internetu. Żeby działał poniższy przykład konieczne jest posiadanie jeszcze jednej z bibliotek POCO::Net, dlatego też podczas instalacji nie należy wykluczać tej biblioteki, w przypadku kompilacji na Linuxie będzie potrzebna flaga -lPocoNet. A oto przykład dla pobierania z HTTP:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Delegate.h>
#include <Poco/URI.h>
#include <Poco/Zip/Decompress.h>
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPMessage.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>

class Decompressor
{
public:
    void decompressFromUrl( const char * url, const char * outputDirectory = ".", bool ignoreDirectoryTree = false, bool keepIncompleteFiles = false )
    {
        Poco::URI uri( url );
        Poco::Net::HTTPClientSession session( uri.getHost(), uri.getPort() );
        std::string path( uri.getPathAndQuery() );
        if( path.empty() )
             path = "/";
       
        Poco::Net::HTTPRequest request( Poco::Net::HTTPRequest::HTTP_GET, path, Poco::Net::HTTPMessage::HTTP_1_1 );
        session.sendRequest( request );
       
        Poco::Net::HTTPResponse response;
        std::istream & rs = session.receiveResponse( response );
       
        Poco::Zip::Decompress decompressor( rs, outputDirectory, ignoreDirectoryTree, keepIncompleteFiles );
       
        const auto errorHandler = Poco::Delegate < Decompressor, std::pair < const Poco::Zip::ZipLocalFileHeader, const std::string > >( this, & Decompressor::onDecompressError );
        const auto successHandler = Poco::Delegate < Decompressor, std::pair < const Poco::Zip::ZipLocalFileHeader, const Poco::Path > >( this, & Decompressor::onDecompressOK );
       
        decompressor.EError += errorHandler;
        decompressor.EOk += successHandler;
       
        decompressor.decompressAllFiles();
       
        decompressor.EError -= errorHandler;
        decompressor.EOk -= successHandler;
    }
   
    void onDecompressError( const void * pSender, std::pair < const Poco::Zip::ZipLocalFileHeader, const std::string >& info )
    {
        std::cerr << "Error during unpacking: " << info.second << '\n';
    }
   
    void onDecompressOK( const void * pSender, std::pair < const Poco::Zip::ZipLocalFileHeader, const Poco::Path >& info )
    {
        std::cout << "Unpacking OK: " << info.second.toString() << '\n';
    }
};

int main()
{
    const char * newestNotepadLink = "http://localhost:8000/npp.zip";
    Decompressor decompressor;
    decompressor.decompressFromUrl( newestNotepadLink );
}

// g++ --std=c++11 -isystem/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/include d3.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/lib -lPocoZip -lPocoNet -lPocoFoundation -pthread && ./a.out
output będzie podobny do tego z poprzedniego przykładu. Należy zwrócić uwagę, że ten kod zadziała tylko do pobierania przez protokół HTTP, natomiast dla HTTPS już nie, a coraz więcej stron udostępniających pliki kładzie nacisk na bezpieczeństwo udostępniając jedynie HTTPS lub FTP.

Przeglądanie zawartości archiwum ZIP

Przed zademonstrowaniem kolejnego przykładu zamieszczę przykład przeglądania archiwum, wszak to również ważna funkcjonalność:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Zip/ZipArchive.h>

void listEntriesInArchive( const char * archiveFileName )
{
    std::ifstream in( archiveFileName );
    if( !in )
    {
        std::cerr << "File " << archiveFileName << " can't be opened!" << std::endl;
        return;
    }
   
    Poco::Zip::ZipArchive arch( in );
   
    for( Poco::Zip::ZipArchive::FileInfos::const_iterator it = arch.fileInfoBegin(); it != arch.fileInfoEnd(); ++it )
    {
        std::cout << it->first << '\n';
    }
}
output może wyglądać w następujący sposób:

katalog1/
katalog1/IMG_20170404_130619.jpg
katalog1/katalog2/
katalog1/katalog2/IMG_20170404_130619.jpg
...
Jeśli kogoś by zainteresował drugi element pary jest to ZipFileInfo, który zawiera pewne dodatkowe informacje typu suma CRC, rozmiar przed/po skompresowaniu, typ pliku, czy to plik/katalog, data modyfikacji pliku, czy zaszyfrowany itp.

Rozpakowywanie tylko niektórych plików w archiwum

Może się zdarzyć, że nie całe archiwum będzie nas interesowało, a jedynie pewien plik/pliki w nim i tylko to co nas interesuje chcemy rozpakować:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Path.h>
#include <Poco/Zip/ZipArchive.h>
#include <Poco/Zip/ZipStream.h> // ZipInputStream
#include <Poco/StreamCopier.h>

void decompressSingleFile( const char * archiveFileName, const char * fileToUnpack )
{
    std::ifstream in( archiveFileName, std::ios::binary );
    if( !in )
    {
        std::cerr << "File " << archiveFileName << " can't be opened!" << std::endl;
        return;
    }
   
    Poco::Zip::ZipArchive arch( in );
    Poco::Zip::ZipArchive::FileHeaders::const_iterator it = arch.findHeader( fileToUnpack );
    if( it == arch.headerEnd() )
    {
        std::cerr << "File " << fileToUnpack << " not found in archive " << archiveFileName << std::endl;
        return;
    }
   
    Poco::Zip::ZipInputStream zipin( in, it->second );
    const std::string unpackedFileName = Poco::Path( fileToUnpack ).getFileName();
    std::ofstream unpackedFile( unpackedFileName.c_str(), std::ios::binary );
    if( !unpackedFile )
    {
        std::cerr << "File " << unpackedFileName << " can't be opened for writing!" << std::endl;
        return;
    }
    Poco::StreamCopier::copyStream( zipin, unpackedFile );
    std::cout << "File " << fileToUnpack << " succesfully unpacked to: " << unpackedFileName << std::endl;
}


int main()
{
    decompressSingleFile( "katalog1.zip", "IMG_20170404_130619.jpg" );
    decompressSingleFile( "katalog1.zip", "katalog1/IMG_20170404_130619.jpg" );
}
output:

File IMG_20170404_130619.jpg not found in archive katalog1.zip
File katalog1/IMG_20170404_130619.jpg succesfully unpacked to: IMG_20170404_130619.jpg
Jak widać musimy podać ścieżkę bezwzględną, jeśli chcemy coś znaleźć.
Uwaga: Wyszukiwanie w archiwach nielokalnych (np. przez HTTP) może nie działać.

Manipulowanie archiwum -dodawanie/usuwanie/przenoszenie i zastępowanie plików

Tutaj wrzucam pełny kod robiący powyższe funkcjonalności z podziałem na funkcje:
C/C++
#include <iostream>
#include <fstream>

#include <Poco/Zip/ZipArchive.h>
#include <Poco/Zip/ZipManipulator.h>

void listEntriesInArchive( const char * archiveFileName )
{
    std::ifstream in( archiveFileName );
    if( !in )
    {
        std::cerr << "File " << archiveFileName << " can't be opened!" << std::endl;
        return;
    }
   
    Poco::Zip::ZipArchive arch( in );
   
    for( Poco::Zip::ZipArchive::FileInfos::const_iterator it = arch.fileInfoBegin(); it != arch.fileInfoEnd(); ++it )
    {
        std::cout << it->first << '\n';
    }
    std::cout << std::endl;
}

void addFileToExistingArchive( const char * archivePatch, const char * fileToAdd, bool backupArchive = false )
{
    std::cout << "Adding file to archive: " << fileToAdd << std::endl;
    Poco::Zip::ZipManipulator manipulator( archivePatch, backupArchive );
   
    manipulator.addFile( fileToAdd, fileToAdd );
   
    manipulator.commit();
}

void deleteFileFromArchive( const char * archivePatch, const char * fileToDelete, bool backupArchive = false )
{
    std::cout << "Deleting file from archive: " << fileToDelete << std::endl;
   
    Poco::Zip::ZipManipulator manipulator( archivePatch, backupArchive );
   
    manipulator.deleteFile( fileToDelete );
   
    manipulator.commit();
}

void renameFileInArchive( const char * archivePatch, const char * sourceFileName, const char * destinationFileName, bool backupArchive = false )
{
    std::cout << "Renaming file in archive: " << sourceFileName << " -> " << destinationFileName << std::endl;
   
    Poco::Zip::ZipManipulator manipulator( archivePatch, backupArchive );
   
    manipulator.renameFile( sourceFileName, destinationFileName );
   
    manipulator.commit();
}

void replaceFileInArchive( const char * archivePatch, const char * existingFileInArchive, const char * localFile, bool backupArchive = false )
{
    std::cout << "Replacing file in archive: " << existingFileInArchive << " with " << localFile << std::endl;
   
    Poco::Zip::ZipManipulator manipulator( archivePatch, backupArchive );
   
    manipulator.replaceFile( existingFileInArchive, localFile );
   
    manipulator.commit();
}

int main()
{
    const char * archiveToModify = "zdjecie.zip";
    listEntriesInArchive( archiveToModify );
   
    addFileToExistingArchive( archiveToModify, "inneZdjecie/zdjecie2.jpg", true );
    listEntriesInArchive( archiveToModify );
   
    renameFileInArchive( archiveToModify, "inneZdjecie/zdjecie2.jpg", "zdjecia/inne/zdjecie2.jpg" );
    listEntriesInArchive( archiveToModify );
   
    replaceFileInArchive( archiveToModify, "zdjecia/inne/zdjecie2.jpg", "inneZdjecie/zdjecie3.jpg" );
    listEntriesInArchive( archiveToModify );
   
    deleteFileFromArchive( archiveToModify, "zdjecia/inne/zdjecie2.jpg" );
    deleteFileFromArchive( archiveToModify, "zdjecia/inne/" );
    deleteFileFromArchive( archiveToModify, "zdjecia/" );
    listEntriesInArchive( archiveToModify );
}

// g++ -isystem/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/include m1.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3-all/zbudowane/lib -lPocoZip -lPocoFoundation -pthread && ./a.out
oraz przykładowy output:

zdjecie.jpg

Adding file to archive: inneZdjecie/zdjecie2.jpg
inneZdjecie/zdjecie2.jpg
zdjecie.jpg

Renaming file in archive: inneZdjecie/zdjecie2.jpg -> zdjecia/inne/zdjecie2.jpg
zdjecia/inne/zdjecie2.jpg
zdjecie.jpg

Replacing file in archive: zdjecia/inne/zdjecie2.jpg with inneZdjecie/zdjecie3.jpg
zdjecia/
zdjecia/inne/
zdjecia/inne/zdjecie2.jpg
zdjecie.jpg

Deleting file from archive: zdjecia/inne/zdjecie2.jpg
Deleting file from archive: zdjecia/inne/
Deleting file from archive: zdjecia/
zdjecie.jpg
Jak widać z ostatnich linijek można usuwać jedynie jeden element naraz, niestety biblioteka nie dostarcza funkcjonalności rekursywnego usuwania.

Wady biblioteki w kwestii plików ZIP

Inne metody archiwizacji biblioteki POCO

Bilioteka ma jeszcze wbudowane mechanizmy konwersji ZLIB i GZIP, informacje na ten temat wraz z przykładami można znaleźć tutaj.

Bibliografia

https://pocoproject.org/docs​/ZipUserGuide.html
https://pocoproject.org/docs​/Poco.Zip.html
https://pocoproject.org/docs​/Poco.html