Chiunque sviluppi software per professione si sarà almeno una volta trovato di fronte alla necessità di creare o viceversa di scompattare un file zip. Spesso la trasmissione di dati sulla rete richiede che si comprimano i file originali in un unico archivio sia per esigenze di spazio sia di semplice unitarietà come fa ad esempio SCORM, un formato di packaging per l'e-learning. Infatti zip è in grado oltre che di ridurre le dimensioni fisiche di un singolo file adottando un algoritmo di compressione, anche di memorizzare la struttura di directory che originariamente conteneva una serie di file. L'algoritmo ZIP, utilizzato per operare la compressione risale ormai al lontano 1989 ed è nato in seguito ad una disputa legale tra Phil Katz e la SEA sulla proprietà di un precedente e meno efficiente algoritmo. In seguito alla perdita della causa, Katz decise di realizzare un nuovo algoritmo più efficace. Fu così che vide la luce ZIP, adottato per la prima volta dall'ormai famoso PKZIP dell'azienda PKWARE di proprietà dello stesso Katz. Fu solo in seguito, durante gli anni '90 che venne creata l'interfaccia grafica cui tutti siamo ormai abituati e che va sotto il nome di Winzip.

Accantonando il fascino della storia di questo algoritmo cerchiamo di capire in che modo si possa dotare una nostra applicazione .NET di una compressione/decompressione di file che usi ZIP. Pensare di scrivere del codice che ricalchi questo algoritmo, per se fattibile è davvero complesso e rischia di risolversi in uno spreco di tempo eccessivo. Per fortuna esistono in rete alcune librerie, create e soprattutto testate da appassionati che permettono di raggiungere lo scopo con semplicità, includendole nei nostri progetti. La libreria che personalmente ritengo più efficace va sotto il nome di SharpZipLib ed è liberamente scaricabile presso il sito di IcSharpCode, il gruppo che ha realizzato anche una apprezzata IDE per .NET.

Una volta scaricato il file della distribuzione binaria dal sito, occorre innanzitutto scompattarlo in una directory per poter usufruire del contenuto. Nella cartella si troveranno un buon numero di directory contenenti tutto il codice sorgente della libreria. Inoltre, nella cartella principale si trovano anche dei file batch, che consentono di installare l'unico assembly nella GAC del proprio PC. Questa è un'operazione molto importante ma non indispensabile. Inserire nella GAC l'assembly consentirà ai vostri programmi di accedere ad esso più rapidamente perchè le operazioni di ricerca e validazione compiute da fusion, il componente di .NET che si occupa del caricamento degli assembly verranno notevolmente semplificate. Tuttavia esso è in grado di reperirlo e caricarlo correttamente anche se tale assembly è presente nella medesima cartella cui fa capo il vostro programma. Qualora desideriate installare la SharpZipLib nella GAC potrete farlo comodamente lanciando il batch InstallGac.bat. Viceversa sarà possibile disinstallarlo con l'altro file UninstallGac.bat. L'assembly ICSharpCode.SharpZipLib.dll, presente nel medesimo file è l'assembly che dovremmo utilizzare nelle nostre applicazioni qualora non desideriamo porlo nella GAC. Tale assembly inoltre dovrà essere referenziato da VisualStudio .NET se si usa tale IDE per sviluppare.

Una volta referenziato l'assembly, arriva il momento di creare il codice necessario. Per questo articolo mi riferirò al codice di esempio che si può trovare nella distribuzione dei sorgenti della libreria. Fulcro principale della libreria sono gli Stream di input e output che dovranno essere usati per comprimere e decomprimere il flusso dei dati. Le classi corrispondenti sono la ZipOutputStream e ZipInputStream che chiaramente si occuperanno rispettivamente di comprimere e decomprimere il flusso. Il meccanismo di compressione prevede innanzitutto che si apra lo stream di output verso il file destinazione e lo stream di input dal file di origine. Quest'ultimo sarà un normale stream non compresso. Naturalmente la logica di funzionamento degli Stream di .NET consentirà non solo di aprire un file fisico, ma anche una connessione di rete, un'area di memoria, etc... Aperto lo stream di output quindi si scandiranno uno ad uno tutti i file da comprimere aprendo uno stream di input da ognuno, e creando così una ZipEntry che consentirà di specificare le informazioni del file in questione nell'indice dello zip. La ZipEntry in particolare raccoglierà il nome del file, la dimensioen ed il CRC. Il CRC, per esteso Cyclic Redundancy Code è una sorta di firma elettronica del file che lo rappresenta in modo quasi del tutto univoco. Il CRC verrà utilizzato in fase di decompressione per verificare che il file estratto sia uguale all'originale e non abbia subito corruzione dall'operazione di compressione/decompressione. Per il calcolo del CRC la libreria mette a disposizione una classe omonima che si incarica di tutti gli oneri.

Inserita la entry nel nuovo Zip con il metodo PutNextEntry() si provvederà a scriverne il contenuto nello stream per mezzo di una semplice Write(). Infine i metodi Finish() e Close() al termine porranno fine all'operazione di compressione. Ecco un breve esempio di quanto finora spiegato:

public static void Main(string[] args)
{
    
string[] filenames = Directory.GetFiles(args[0]);
    
    Crc32 crc = 
new Crc32();
    ZipOutputStream s = 
new ZipOutputStream(File.Create(args[1]));
    
    s.SetLevel(6); 

    
foreach (string file in filenames)
    {
        FileStream fs = File.OpenRead(file);
        
        
byte[] buffer = new byte[fs.Length];
        fs.Read(buffer, 0, buffer.Length);
        ZipEntry entry = 
new ZipEntry(file);            
        entry.DateTime = DateTime.Now;
        entry.Size = fs.Length;
        fs.Close();
        
        crc.Reset();
        crc.Update(buffer);
        entry.Crc  = crc.Value;
        
        s.PutNextEntry(entry);            
        s.Write(buffer, 0, buffer.Length);
    }
    
    s.Finish();
    s.Close();
}

Esempio 1 - Creare il file ZIP
 
 
Utile segnalare che per mezzo del metodo SetLevel() si può specificare con un numero da 0 a 9 il livello di compressione dove 0 disabilita del tutto la compressione ma si limita a mantenere la struttura delle directory.
 
L'operazione inversa non è più complessa di quella appena illustrata. Richiede semplicemente che si tenga conto della struttura presente nel file zip e che sulla base di essa si ricreino le directory e i file di destinazione. Gli stream di input e output saranno questa volta invertiti e sarà quello di output ad essere ripetutamente aperto e chiuso per scrivere il contenuto decompresso. Ecco di seguito il codice relativo:
 
 
public static void Main(string[] args)
{
    ZipInputStream s = 
new ZipInputStream(File.OpenRead(args[0]));
    ZipEntry theEntry;

    
while ((theEntry = s.GetNextEntry()) != null)
    {
        
        Console.WriteLine(theEntry.Name);
        
        
string directoryName = Path.GetDirectoryName(theEntry.Name);
        
string fileName      = Path.GetFileName(theEntry.Name);
        
        Directory.CreateDirectory(directoryName);
        
        
if (fileName != String.Empty)
        {
            FileStream streamWriter = File.Create(theEntry.Name);
            
int size = 2048;
            
byte[] data = new byte[2048];

            
while (true
            {
                size = s.Read(data, 0, data.Length);

                
if (size > 0)
                    streamWriter.Write(data, 0, size);
                
else 
                    break
;
            }
            
            streamWriter.Close();
        }
    }
    s.Close();
}
 
Esempio 2 - Decomprimere uno zip
 
L'unica reale differenza la si può riscontrare nella necessità di verificare la presenza di directory nel file e di ricrearle nella directory di destinazione per mantenere la struttura oiginale. Chiudo questo lungo articolo suggerendo lo studio del progetto HtpCompressionModule presente tra i sorgenti. Esso implementa un modulo per ASP.NET che consente la compressione/decompressione zip dello stream di output/input da e verso il browser che la supporti. Utile potrà essere anche la lettura del mio articolo, scritto per UgiDotNet, "Inside HttpHandlers" che spiega come realizzare un handler che mostri la struttura web di un file zip come se si trattasse di unsa normale virtual directory.
 
tags: - categories:

Commenti (7) -

# | Di .NET e di altre amenita' | 28.06.2005 - 01.23

# | Radicalmente | 19.09.2005 - 22.25

# | gaspare | 16.03.2007 - 22.58

Ma SharpZipLib funziona anche con il .NET Compact Framework 2.0?
Ho provato il tuo codice e non va!

grazie

# | Andrea Boschin | 16.03.2007 - 23.01

Non credo che possa funzionare però non essendo esperto di CF non posso esserti di aiuto. L'argomento però è interessante, che ne dici di farci sapere? Ciao!

# | Dario | 19.01.2009 - 21.15

Ho provato il tuo codice in compact Framework...è fantastico
per il nostro amico bisogna che nei riferimenti includi anche la DLL altrimenti è logico che non funziona

# | Stefano P. | 29.03.2009 - 02.25

Questo mi serviva proprio. Come sempre sei fantastico. (ti ricordi di me a Pistoia al DNW WorkShop?)

# | Max | 10.08.2009 - 23.28

Esattamente quello che cercavo.
Problema risolto in 5 minuti!

Grazie!

Aggiungi Commento