GZIP (ili 3 linije koda)

U nekom trenutku mi je dosadilo da se natežem sa backup-om SQL baza i stoga rešim da napravim sopstveni servis koji će to da radi umesto SQLAgent-a. Zašto koristiti gotovo alat kad možeš da isprogramiraš svoj? U svakom slučaju .bak file-ovi su, kako drugačije, ogromni, is toga rešim da ih kompresujem pošto je obično stepen kompresije na ovim file-ovima spektakularan, a pošto ih još kopiram u folder koji se sinhronizuje sa popularnim cloud servisom (nisu platili za reklamu pa ih ovde neću više ni pominjati), zip-ovanje je postalo imperative.

Volim uvek prvo da proverim na StackOverflow-u šta ostali kažu i moram da priznam da sam u startu bio skeptičan prema 3rd party bibliotekama (DotNetZip i SharpZipLib) ali da me prilično iznenadila činjenica da Microsoft direktno u .NET-u podržava gzip format pa reših da isprobam kako to funkcioniše.

Primer metode koja čita file na file sistemu i kreira novi kompresovani file:

        public static void CompressFile(string oldFileName, string newFileName)

        {

            var bytes = System.IO.File.ReadAllBytes(oldFileName);

            using (FileStream fs = new FileStream(newFileName, FileMode.Create))

            using (GZipStream zipStream = new GZipStream(fs, CompressionMode.Compress, false))

            {

                zipStream.Write(bytes, 0, bytes.Length);

            }

        }

 

Ono što mi nije bilo očigledno baš odmah jeste činjenica da je pri pravljenju gzip neophodno kao ekstenziju navesti i staru ekstenziju i dodati na nju “.gz” ekstenziju

CompressFile(backupFolder + item.DatabaseName + ".bak", outputFolder + item.DatabaseName + ".bak.gz");

inače ćete izgubiti originalnu ekstenziju file-a.

Više file-ova ili do viđenja GZIP-u

Tu bi se priča i završila da nam nije palo na pamet da pravimo zip-ove sa više file-ova u njima. Očigledan razlog je bila potreba da, kada se generiše gomila pojedinačnih naloga za prenos sredstava u našem softveru, klijent može da preuzme jedan file, raspakuje ga i učita u online software banke svaki od naloga.

Sjajno, pomislih, imam gotovu metodu za kompresiju file-ova a onda je usledilo razočaranje – gzip radi samo sa jednim file-om. Slažem se da je to očigledno, pogotovo iz perspektive da sam bio svestan priče oko ekstenzija gzip file-a ali je svakako bilo iznenađenje.

Proveravajući šta je dostupno za kompresiju foldera naleteo sam na Microsoft-ovu ZipPackage klasu. Ko kaže da Microsoft ne može potpuno da vas iznenadi?

Pošto nama gomila naloga za prenos u xml obliku koju korisnik generiše samo da bih preuzeo kod sebe i učitao u softver banke apsolutno nije potrebna i predstavlja neželjeni višak na našem serveru evo kako možete da napravite zip folder od xml dokumenata iz stream-a, bez prethodnog snimanja xml dokumenata na file sistem

Primer klase koje pravi zip file iz stream-a

   public class ZipUtility

    {

        Package package;




        public ZipUtility(Stream s)

        {

            package = ZipPackage.Open(s, FileMode.Create);

        }




        public void Add(Stream stream, string Name)

        {

            Uri partUriDocument = PackUriHelper.CreatePartUri(new Uri(Name, UriKind.Relative));

            PackagePart packagePartDocument = package.CreatePart(partUriDocument, "");




            CopyStream(stream, packagePartDocument.GetStream());

            stream.Close();

        }




        private static void CopyStream(Stream source, Stream target)

        {

            const int bufSize = 0x1000;

            byte[] buf = new byte[bufSize];

            int bytesRead = 0;

            while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)

                target.Write(buf, 0, bytesRead);

        }




        public void Close()

        {

            package.Close();

        }

    }

 

Primer metode koja poziva generisanje zip file-a iz stream-a

public static string CreateZipFileWebServer(string fileName, string fileContent, string prefixFile = "")

        {

            FileStream str = null;

            Stream memstr = null;

            ZipUtility zip = null;




            string tempFolder = HttpContext.Current.Server.MapPath("~/files/Temp");




            try

            {

                string zipFile = Path.Combine(tempFolder, prefixFile + fileName) + ".zip";




                str = System.IO.File.Open(zipFile, FileMode.Create);

                zip = new ZipUtility(str);




                memstr = GenerateStreamFromString(fileContent);

                zip.Add(memstr, prefixFile + fileName + ".xml");

                memstr.Close();




                zip.Close();

                str.Close();




                return "Temp/" + prefixFile + fileName + ".zip";

            }

            catch (Exception ex)

            {

                if (str != null) str.Close();

                if (memstr != null) memstr.Close();

                if (zip != null) zip.Close();

                throw (ex);

            }

        }

 

Primer metode koja generiše zip file za više xml dokumenata (u Dictionary objektu se nalazi sadržaj dokumenta i naziv dokumenta)

public static string CreateZipFolderWebServer(List<ERPDictionary<string, string>> listXml, string prefixFolder = "", string prefixFile = "")

        {

            FileStream str = null;

            Stream memstr = null;

            ZipUtility zip = null;




            string tempFolder = HttpContext.Current.Server.MapPath("~/files/Temp");




            try

            {

                string guid = prefixFolder + Guid.NewGuid().ToString("N").Substring(0, 32);

                string zipFile = Path.Combine(tempFolder, guid) + ".zip";




                str = System.IO.File.Open(zipFile, FileMode.Create);

                zip = new ZipUtility(str);




                foreach (ERPDictionary<string, string> xml in listXml)

                {

                    memstr = GenerateStreamFromString(xml.Value);

                    zip.Add(memstr, prefixFile + xml.Key);

                    memstr.Close();

                }

                zip.Close();

                str.Close();




                return "Temp/" + guid + ".zip";

            }

            catch (Exception ex)

            {

                if (str != null) str.Close();

                if (memstr != null) memstr.Close();

                if (zip != null) zip.Close();

                throw (ex);

            }

        }

 

Pošto mene užasno nervira kada nemam kompletan primer sledi još metoda koja generiše stream iz string-a koji predstavlja sadržaj xml dokumenta

        public static Stream GenerateStreamFromString(string s)

        {

            MemoryStream stream = new MemoryStream();

            StreamWriter writer = new StreamWriter(stream);

            writer.Write(s);

            writer.Flush();

            stream.Position = 0;

            return stream;

        }

kao i definicija klase koje se prosleđuje kao prvi parametar prilikom poziva metode koja pravi zip file na osnovu više dokumenata

    public class ERPDictionary<T1, T2> : IBasicModel

    {

        [IgnoreDataMember]

        public bool IsEmpty { get; set; } = true;

        public T1 Key { get; set; }

        public T2 Value { get; set; }

    }




    public interface IBasicModel

    {

        bool IsEmpty { get; set; }

    }

 

Nazivi file-ova

Da bi obezbedili jedinstvenost naziva file-ova u sistemu uveli smo generičko kreiranje naziva file-ova pomoću

Guid.NewGuid().ToString("N").Substring(0, 32);

Ovom komandom se generšise guid bez “-“. Pošto je ovo potpuno nerazumljivo kako korisniku tako i nama uveli smo dodatni prefix imena file-a (i/ili foldera) kao parameter poziva ovih funkcija

Nema ništa savršeno

Na kraju kada je sve proradilo – finalno iznenađenje. U samom zip-u se misteriozno pojavljuje novi file [Content_Type].xml koji ima sledeći sadržaj:

<?xml version="1.0" encoding="utf-8"?>

<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">

                <Default Extension="xml" ContentType="" />

</Types>

 

Upravo kada sam poželeo da odustanem od svega i pređem na 3rd party biblioteke pronašao sam sledeće objašnjenje

The content types part is always required, and its name and location is fixed: it must be [Content_Types].xml, and it must be in the root of the package.  That’s so that a consumer always knows where to look to see the manifest of content types in the package, which is something a consumer usually wants to do before anything else

Ovaj file je integralni deo nečega što se zove Open Packaging Convention i on je zapravo dodatni sigurnosni mehanizam ako želite kroz .NET i da raspakujete document jer izlistava sve tipove file-ova koji su unutar zip-a.

Tako da smo rešili da ostavimo ovo rešenje, pre svega očekujući da će, vrlo verovatno, browser-i u budućnosti možda i zahtevati zip file-ove kreirane po ovom standardu

p

U ovom članku

  • Jednostavno GZIP
  • Doviđenja GZIP
  • Nazivi fajlova
  • Niko nije savršen