متاسفانه در زبان هاي #C و VB امکاني براي زيپ کردن فايل ها به طور مستقيم وجود ندارد . با اين وجود در زبان #J راهي براي zip و unzip نمودن فايل ها وجود دارد.
در اين مقاله يک کتابخانه (Library) خواهيم نوشت که قابليت استفاده مجدد داشته و مي توانيم از آن در تمام پروژه هاي تحت ويندوز و وب براي zip و unzip کردن فايل ها استفاده کنيم .
مقدمه :
در بسياري مواقع ، در برنامه هايي که مي نويسيم نياز داريم که بعضي فايل ها مانند اسناد و فايل هاي XML و غيره را به صورت فشرده ذخيره کنيم و يا فايلي که به صورت فشرده ، ذخيره شده است را از حالت فشردگي خارج نماييم .
مثلا فرض کنيد که شما در برنامه وب خود ، امکاني داده ايد که کاربران اسناد و يا فايل هاي خود را در صفحه اي براي شما آپلود کنند و بديهي است که ترجيح مي دهيد فايل ها به صورت زيپ شده در فضاي سايت شما ذخيره شوند (به دلايل محدوديت فضا و غيره).
براي zip و unzip کردن فايل ها در زبان #C معمولا از يکي از اين 3 طريق استفاده مي شود :
-
استفاده از کامپوننت ها و برنامه هاي مکمل (تهيه شده توسط ديگران)
-
استفاده از کامپوننت هاي متن باز (Open Source) موجود.
-
پياده سازي الگوريتم زيپ سازي فايل ها ، در برنامه
گزينه اول نيازمند دريافت مجوز استفاده و خريد کامپوننت و پرداخت هزينه مي باشد .
گزينه دوم يعني استفاده از کامپوننت هاي متن باز ، بسيار جالب مي باشد ولي با اين وجود مشکلات برنامه هاي متن باز ، اينجا نيز وجود دارد . يعني مشکل باگ هاي احتمالي و بروز رساني و پشتيباني که مي تواند موجب پديد آمدن مشکلات بزرگي شود.
روش سوم بسيار مشکل بوده و نياز مند صرف هزينه و رمان زيادي مي باشد.
خوشبختانه زبان #J که يکي از زبان هاي پلتفرم دات نت مي باشد ، داراي امکاني براي zip و unzip کردن فايل ها به وسيله کد مي باشد.
مزاياي استفاده از #J براي zip و unzip کردن فايل ها :
-
#J يکي از زبان هاي موجود در پلتفرم دات نت بوده و بدون هزينه اضافي در دسترس ما مي باشد.
-
زبان #J توسط شرکت مايکروسافت پشتيباني و بروزرساني مي شود .
-
ديگر نيازي به استفاده از برنامه هاي مکمل نداريم.
در اين قسمت با استفاده از کلاس هاي #J ، کامپوننتي در #C ايجاد خواهيم کرد و از آن در تمام برنامه هاي خود استفاده مي کنيم .
ايجاد يک کتابخانه از کلاس ها :
براي شروع کار يک پروژه Class Library در زبان #C ايجاد نموده و در آن با استفاده از کلاس ها و توابع #J کامپوننت را ايجاد خواهيم کرد.
يک پروژه Class Library ايجاد کنيد. در Solution Explorer روي نام پروژه کليک راست نموده و Add Reference را انتخاب کنيد. همانطور که در شکل مي بينيد ، اسمبلي vjslib را به برنامه اضافه کنيد.
|
سپس فضا هاي نامي (namespace) زير را به برنامه اضافه کنيد
-
java.util;
-
java.util.zip;
-
java.io; |
فضاي نامي java.util شامل کلاس هاي سودمندي براي ما مي باشد . فضاي نامي java.util.zip شامل کلاس هاي اصلي مرتبط به ايجاد فايل هاي زيپ مي باشد . و در نهايت فضاي نامي java.io شامل کلاس هاي مرتبط به IO مي باشد.
ما از کلاس هاي زير که در فضاهاي نامي ذکر شده ، مي باشد ، استفاده مي کنيم.
ZipFile
ZipEntry
InputStream
OutputStream
FileInputStream
FileOutputStream
ZipOutputStream
Enumeration |
کلاس ZipFile امکان ايجاد يک فايل زيپ از طريق برنامه نويسي مي دهد. يک ZipFile شامل صفر و يا بيشتر از اشياء ZipEntry و محتواي واقعي فايل هاي زيپ مي باشد.
کلاسهاي InputStream و OutputStream و FileInputStream و FileOutputStream ارائه دهنده استريم هاي به ترتيب اشاره گر به حافظه و مبتني برفايل مي باشند.
کلاس ZipOutputStream يک استريم قابل نوشتن ( writeable ) ايجاد نموده که به يک فايل زيپ اشاره مي کند . اين استريم مي تواند براي نوشتن اشياء ZipEntry و محتواي آنها در يک فايل زيپ استفاده شود.
ايجاد فايل هاي زيپ :
قبل از اينکه به نوشتن کدهاي مربوطه براي ايجاد و استخراج ( Extract ) فايل هاي زيپ بپردازيم ، اجازه بدهيد که به معرفي متدهايي که ما در آينده به آن نياز خواهيم داشت بپردازيم.
فشرده سازي تعدادي از عناصر، در فقط يک فايل زيپ :
متد GetZippedItems يک شي ZipFile را به عنوان پارامتر گرفته و يک ليست ژنريک از اشياء ZipEntry موجود در فايل را برمي گرداند.
متد GetZippedItems را در زير مشاهده مي کنيد.
private static List<ZipEntry> GetZippedItems(ZipFile file)
{
List<ZipEntry> entries = new List<ZipEntry>();
Enumeration e = file.entries();
while (true)
{
if (e.hasMoreElements())
{
ZipEntry entry = (ZipEntry)e.nextElement();
entries.Add(entry);
}
else
{
break;
}
}
return entries;
} |
متد GetZippedItems يک شي ZipFile را به عنوان پارامتر گرفته و يک ليست ژنريک از اشياء ZipEntry را برمي گرداند.
داخل متد ، يک ليست ژنريک از نوع ZipEntry به نام entries ايجاد نموده ايم و سپس با فراخواني متد entries يک داده شمارشي (Enumeration) از اشياء ZipEntry ها پر مي کنيم و در نهايت ليست پر شده را برمي گردانده مي شود .
کپي کردن استريم ها :
در زمان اضافه کردن يا برداشتن فايل ها از يک فايل زيپ موجود ، ما نياز داريم که محتواي اصلي فايل را از استريم مبدأ به استريم مقصد کپي کنيم .
بنابراين نيازمند متدي هستيم تا اين عمل را براي ما انجام دهد. اين متد را CopyStream مي ناميم و در زير آن را مشاهده مي کنيد.
private static void CopyStream(InputStream source, OutputStream destination)
{
sbyte[] buffer = new sbyte[8000];
int data;
while (true)
{
try
{
data = source.read(buffer, 0, buffer.Length);
if (data > 0)
{
destination.write(buffer, 0, data);
}
else
{
return;
}
}
catch (Exception ex)
{
string msg = ex.Message;
}
}
} |
همانطور که مشاهده مي کنيد ، متد CopyStream دو پارامتر مي گيرد ، يکي از نوع InputStream که همان استريم منبع مي باشد و ديگري از نوع OutputStream که استريم مقصد مي باشد. با استفاده از متد read مربوط به استريم منبع ، به اندازه قطعات 8000 بايتي از آن خوانده و در استريم مقصد با استفاده از متد write موجود در OutputStream مي نويسيم .
کپي کردن اشياء ZipEntry :
کلاس هاي مربوط به فشرده سازي در #J به شما اجازه اضافه کردن و حذف کردن ( Remove ) فايل ها ، به طور مستقيم ، از يک فايل زيپ را نمي دهند. تنها راه اضافه کردن و حذف کردن فايل ها از يک فايل زيپ شده ، اين است که يک فايل زيپ جديد با عناصر مورد نياز آن ، ايجاد نموده و جايگزين ( Replace ) فايل زيپ اصلي نماييم .
بنابراين ما نيازمند متدي هستيم که اشياء ZipEntry را از يک فايل زيپ به فايل ديگر که مورد نظر ما مي باشد ، کپي کند. ما اين متد را CopyEntries مي ناميم و به صورت دو متد Overloads شده مي نويسيم . در زير اين متد را مشاده مي کنيد.
private static void CopyEntries(ZipFile source, ZipOutputStream destination)
{
List<ZipEntry> entries = GetZippedItems(source);
foreach (ZipEntry entry in entries)
{
InputStream s = source.getInputStream(entry);
destination.putNextEntry(entry);
CopyStream(s, destination);
destination.closeEntry();
s.close();
}
} |
private static void CopyEntries(ZipFile source, ZipOutputStream destination, string[] entryNames)
{
List<ZipEntry> entries = GetZippedItems(source);
for (int i = 0; i < entryNames.Length; i++)
{
foreach (ZipEntry entry in entries)
{
if (entry.getName() == entryNames[i])
{
InputStream s = source.getInputStream(entry);
destination.putNextEntry(entry);
CopyStream(s, destination);
destination.closeEntry();
s.close();
}
}
}
} |
اولين متد دو پارامتر دريافت مي کند. پارامتر اول ZipFile منبع مي باشد که شامل ZipEntry هايي است که قرار است کپي شوند. پارامتر دوم ZipOutputStream هدف مي باشد که قرار است ZipEntry ها ، در آن نوشته شوند.
متد Overloads شده دوم ، داراي سه پارامتر مي باشد . 2 پارامتر اول همانند متد قبلي مي باشند و پارامتر سوم حاوي نام entry هاي مشخصي مي باشد که ما در نظر داريم کپي شوند.
فرق اين متد با متد قبلي در اين است که در متد اول تمام entry ها به استريم مقصد کپي مي شوند ولي در متد دوم ، فقط entry هاي مشخصي که ما در پارامتر سوم نام آن ها را ارسال مي کنيم ، به استريم مقصد کپي مي شوند ( در ادامه مقاله از اين دو متد استفاده خواهيم کرد و تفاوت آنها مشاده خواهيد کرد ).
هر دوي متد هاي Overloads شده بالا ، با استفاده از متد GetZippedItems که قبلا نوشته ايم ، ليستي از ZipEntry ها را بازيابي مي کنند و در داخل ليستي به نام entries قرار مي دهند . سپس entries به ZipOutputStream انتقال مي يابد . متد putNextEntry از کلاس ZipOutputStream ، يک ZipEntry را که بايد به فايل زيپ اضافه شود ، را گرفته و در فايل زيپ مي نويسد.
متد getInputStream از کلاس ZipFile ، يک ZipEntry را گرفته و يک InputStream که به يک entry اشاره مي کند را برمي گرداند.( اين استريم توسط متد CopyStream که قبلا نوشته ايم براي خواندن اطلاعات از entry مربوطه استفاده مي شود )
به ياد داشته باشيد که ZipEntry به سادگي متا ديتاي مربوط به entry ، را ايجاد مي کند و ZipFile منبع ، با استفاده از متد getInputStream ، محتواي واقعي فايل را ايجاد خواهد کرد که به شکل InputStream مي باشد.
در نهايت با فراخواني متد closeEntry از کلاس ZipOutputStream نوشتن entry پايان مي يابد.
اضافه کردن entries ، به فايل زيپ موجود :
متدي به نام AddEntries مي نويسيم که اشياء ZipEntry را به يک فايل زيپ اضافه مي کند.
private static void AddEntries(ZipFile file, string[] newFiles)
{
string fileName = file.getName();
string tempFileName = Path.GetTempFileName();
ZipOutputStream destination = new ZipOutputStream(new FileOutputStream(tempFileName));
try
{
CopyEntries(file, destination);
if (newFiles != null)
{
foreach (string f in newFiles)
{
ZipEntry z = new ZipEntry(f.Remove(0, Path.GetPathRoot(f).Length));
z.setMethod(ZipEntry.DEFLATED);
destination.putNextEntry(z);
try
{
FileInputStream s = new FileInputStream(f);
try
{
CopyStream(s, destination);
}
finally
{
s.close();
}
}
finally
{
destination.closeEntry();
}
}
}
}
finally
{
destination.close();
}
file.close();
System.IO. File.Copy(tempFileName, fileName, true);
System.IO. File.Delete(tempFileName);
} |
همانطور که مي بينيد با استفاده از متد getName مسير کامل فايل را بدست مي آوريم و با استفاده از متد GetTempFileName ، از کلاس Syste.IO ، يک نام فايل موفقتي ( temporary file name ) ايجاد مي کنيم. ممکن است اکنون براي شما اين سوال به وجود آمده باشد که فايل موقتي را براي جه ما ايجاد مي کنيم؟!
متد AddEntries را زماني فراخواني مي کنيم که بخواهيم يک فايل زيپ جديد ايجاد نموده و يا عناصري را به يک فايل زيپ موجود ، اضافه کنيم .
کلاس هاي فشرده سازي دز #J اجازه تغيير دادن يک فايل زيپ را به صورت مستقيم نمي دهند ، بنابراين بايد يک فايل زيپ جديد ، با عناصر مورد نيازمان را به طور موقت ايجاد نموده و جايگزين ( Replace ) فايل قديمي نماييم . نام اين فايل موقت را با استفاده از متد GetTempFileName بدست مي آوريم.
سپس يک شي از نوع ZipOutputStream ايجاد کرديم که به اين فايل موقت اشاره مي کند. در اينجا متد CopyEntries که قبلا نوشته ايم را فراخوني مي کنيم . اين متد entry ها را از فايلي که به عنوان پارامتر اول به آن ارسال مي شود، مي گيرد و به داخل يک ZipOutputStream ( پارامتر دوم ) کپي مي کند.
اگر شما در حال ايجاد يک فايل زيپ جديد مي باشيد ، متد CopyEntries ، هيچ entry را کپي نمي کند ولي اگر در حال اضافه کردن عناصري به فايل زيپي که در حال حاضر وجود دارد ، هستيد اين متد تمام entry هاي موجود در اين فايل زيپ را به فايل زيپ موقتي جديد کپي خواهد کرد.سپس در حلقه foreach همه فايل هايي که بايد زيپ شوند به ZipFile اضافه مي شوند .
هر فايل زيپ شده توسط کلاس ZipEntry ، قابل ارائه خواهد بود. سازنده ( Constructor ) کلاس ZipEntry ، نام entry مربوطه را مي گيرد.
با استفاده از تابع setMethod ، متد فشرده سازي به حالت پيشفرض ، تنظيم مي شود.
ZipEntry اي که به تازگي ايجاد شده است ، با استفاده از متد putNextEntry به ZipOutputStream اضافه شده است.
يک ZipEntry فقط متاديتاي مربوط به يک entry را ارائه مي دهد و ما هنوز نياز داريم که محتواي واقعي فايل را به فايل زيپ اضافه کنيم . اين عمل را با استفاده از متد CopyStream که قبلا نوشته ايم امکان پذير مي باشد.
حذف کردن entry ها از يک فايل زيپ شده :
در مقابل متد AddEntries ، متد RemoveEntries ، اشياء ZipEntry را از يک فايل زيپ شده مشخص حذف مي کند. اين متد در زير نشان داده شده است.
private static void RemoveEntries(ZipFile file, string[] items)
{
string fileName = file.getName();
string tempFileName = Path.GetTempFileName();
ZipOutputStream destination = new ZipOutputStream(new FileOutputStream(tempFileName));
try
{
List<ZipEntry> allItems = GetZippedItems(file);
List<string> filteredItems = new List<string>();
foreach (ZipEntry entry in allItems)
{
bool found = false;
foreach (string s in items)
{
if (s != entry.getName())
{
found = true;
}
}
if (found)
{
filteredItems.Add(entry.getName());
}
}
CopyEntries(file, destination, filteredItems.ToArray());
}
finally
{
destination.close();
}
file.close();
System.IO. File.Copy(tempFileName, fileName, true);
System.IO. File.Delete(tempFileName);
} |
متد RemoveEntries يک ZipEntry را به در پارامتر اول گرفته و ليست اسامي entry هايي که بايد از اين فايل حدف شوند را در پارامتر دوم خود مي گيرد.
متد RemoveEntries بسيار شبيه به متد AddEntries مي باشد به استثناي اينکه ديگر ، entry هاي مشخص شده ، کپي نمي شوند.
به کدهايي که به صورت bold مي باشند توجه کنيد . در اين قسمت ليستي که حاوي کل entry ها مي باشد و ليستي که حاوي نام entry هايي که بايد حذف شوند ، مقايسه مي شوند. اگر نام entry جزو نام هايي که بايد حذف شوند نباشد ، به ليست filteredItems اضافه مي شود و در غير اين صورت اضافه نمي شود.
در نهايت ليست filteredItems ، حاوي نام entry هايي مي باشد که بايد کپي شوند . در انتها متد Overloads شده دوم CopyEntries را فراخواني مي کنيم تا فقط entry هاي مشخص شده را براي ما کپي کند.
ايجاد يک فايل زيپ جديد :
به منظور ايجاد يک فايل زيپ جديد ، يک متد استاتيک با نام CraeteZipFile در داخل کلاس مي نويسيم . متد CraeteZipFile دو پارامتر مي گيرد . پارامتر اول مسير و نام فايل زيپ مي باشد . و پارامتر دوم آرايه اي از نام هاي عناصري است که قرار است زيپ شوند
public static void CreateZipFile(string filename, string[] items)
{
FileOutputStream fout = new FileOutputStream(filename);
ZipOutputStream zout = new ZipOutputStream(fout);
zout.close();
ZipFile zipfile = new ZipFile(filename);
AddEntries(zipfile, items);
} |
همانطور که ملاحظه مي کنيد يک شي از نوع کلاس FileOutputStream ايجاد نموده ايم . کلاس FileOutputStream يک استريم قابل نوشتن روي يک فايل را ارائه مي دهد .
سازنده ( Constructor) اين کلاس مسير فايلي را که ما قصد داريم استريم را در آن بنويسيم ، دريافت مي کند. سپس يک شي از نوع کلاس ZipOutputStream ايجاد نموده ايم . کلاس ZipOutputStream يک استريم که قابليت نوشتن ( writeable ) روي يک فايل زيپ را دارد ، ارائه مي کند .
با فراخواني متد close مربوط به کلاس ZipOutputStream يک فايل خالي زيپ به وجود مي آيد. اکنون يک شي از نوع کلاس ZipFile ايجاد کرده ايم. کلاس ZipFile يک فايل زيپ را به ما ارائه مي کند و محتواي فايل زيپ را ايجاد مي کند.
در انتهاي متد AddEntries را که قبلا نوشته ايم ، فراخواني کرده ايم.
اضافه کردن فايل ها به يک فايل زيپ موجود :
مواقعي وجود دارد که نياز داريم ، فايل هايي را به فايل زيپي که هم اکنون موجود است ، اضافه کنيم . متد AddToZipFile دقيقا اين کار را انجام مي دهد.
public static void AddToZipFile(string filename, string[] items)
{
ZipFile file = new ZipFile(filename);
AddEntries(file, items);
} |
اين تابع مسير فايل زيپ شده را به عنوان پارامتر اول و آرايه اي از نامهاي فايل هايي که بايد به اين فايل زيپ اضافه شوند را به عنوان پارامتر دوم مي گيرد . وظيفه باقي کد ها نيز قبلا شرح داده شده است .
حذف کردن فايل هايي از يک فايل زيپ شده :
براي انجام اين کار متدي به نام RemoveFromZipFile نوشته ايم که در زير آن را مي بينيد.
public static void RemoveFromZipFile(string filename, string[] items)
{
ZipFile file = new ZipFile(filename);
RemoveEntries(file, items);
} |
استخراج ( Extract ) يک فايل زيپ :
تا اينجا شما ياد گرفته ايد که چگونه فايل را فشرده سازيد ( زيپ کنيد) و به يک فايل زيپ ، عناصري را اضافه و يا حذف کنيد. اکنون فرا خواهيد گرفت که چگونه فايل هاي موجود در يک فايل زيپ را استخراج کنيد . براي اين کار متدي به نام ExtractZipFile مي نويسيم .
public static void ExtractZipFile(string zipfilename, string destination)
{
ZipFile zipfile = new ZipFile(zipfilename);
List<ZipEntry> entries = GetZippedItems(zipfile);
foreach (ZipEntry entry in entries)
{
if (!entry.isDirectory())
{
InputStream s = zipfile.getInputStream(entry);
try
{
string fname = System.IO.Path.GetFileName(entry.getName());
string dir = System.IO.Path.GetDirectoryName(entry.getName());
string newpath = destination + @"\" + dir;
System.IO. Directory.CreateDirectory(newpath);
FileOutputStream dest = new FileOutputStream(System.IO.Path.Combine(newpath, fname));
try
{
CopyStream(s, dest);
}
finally
{
dest.close();
}
}
finally
{
s.close();
}
}
}
} |
متد ExtractZipFile ، مسير فايل زيپ را به عنوان پارامتر اول و مسير پوشه اي که فايل هاي زيپ شده بايد در آن استخراج شوند را به عنوان پارامتر دوم مي گيرد. يک شي از نوع ZipFile به وجود آورديم که entries را توسط متد GetZippedFileNames که قبلا نوشته ايم ، بدست مي آورد.
سپس در يک حلقه foreach تمام entry ها پيمايش مي شوند . در هر بار پيمايش حلقه foreach ، يک entry مشخص به درون پوشه مورد نظر استخراج مي شود. متد getInputStream از کلاس ZipFile ، يک InputStream براي entry مشخص ، برمي گرداند.
اين استريم به عنوان استريم منبع عمل مي کند. متد getName از کلاس ZipEntry ، نام کامل entry را برمي گرداند. توجه داشته باشيد که اين نام شامل نام درايو نمي باشد. نام فايل با استفاده از نام entry و مسير پوشه مقصد محاسبه مي شود.
يک FileOutputStream ايجاد کرده ايم تا فايل استخراج شده را روي هارد ديسک بنويسيم. و در نهايت با استفاده از متد CopyStream اطلاعات را از InputStream منبع به FileOutputStream مقصد انتقال مي دهيم .
خوب . تمام شد . کتابخانه کلاس ما کامل شد.
استفاده از کتابخانه کلاس (Class Library) :
استفاده از کتابخانه کلاس نسبتا ساده مي باشد. شما نياز داريد که متد هاي لازم را از کلاس ZipFileHelper که امروز نوشتيم ، فراخواني کنيد. براي مثال براي ايجاد يک فايل زيپ بايد متد CreateZipFile را فراخواني کنيد و براي استخراج فايل هاي موجود در يک فايل زيپ بايد متد ExtractZipFile را فراخواني کنيد.
کد مثال برنامه را مي توانيد از لينک بالاي صفحه دانلود کنيد که شامل يک برنامه تحت ويندوز است که ما از اين کتابخانه در آن استفاده نموده ايم . تصويري از برنامه را در پايين مي بينيد.
منبع : DotNetBips