01.03.2020. Josip Bugarčić
u kategoriji Programiranje
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.
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; }
}
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
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