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:
|
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:
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.htmlhttps://pocoproject.org/docs/ZipUserGuide.htmlTworzenie archiwum
Używane funkcje i obiekty
W celu tworzenia archiwum używamy obiektu
Poco::Zio::Compress:
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:
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();
Przykład kompresji jednego pliku
Zacznijmy od najprostrzego przykładu -dodania do archiwum jednego pliku:
#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();
}
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.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):
#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();
}
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:
#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();
}
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:
#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: 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.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:
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ć:
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:
compressor.addRecursive( "katalog" );
to otrzymamy strukturę:
plik1
jeśli natomiast zawołamy:
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:
#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 );
}
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)
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:
Poco::Zip::Decompress(
std::istream & in,
const Poco::Path & outputDir,
bool flattenDirs = false,
bool keepIncompleteFiles = false
);
Oraz przy użyciu metody:
Decompress::decompressAllFiles();
Proste rozpakowywanie całego archiwum
Więc weźmy najprostszy przykład rozpakowywania całego archiwum:
#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:
#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:
#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";
}
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:
#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 );
}
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ść:
#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ć:
#include <iostream>
#include <fstream>
#include <Poco/Path.h>
#include <Poco/Zip/ZipArchive.h>
#include <Poco/Zip/ZipStream.h>
#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:
#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 );
}
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.htmlhttps://pocoproject.org/docs/Poco.Zip.htmlhttps://pocoproject.org/docs/Poco.html