Click here to Skip to main content
15,897,226 members
Articles / General Programming / Algorithms

Processing Standard Zip Files with C# compression/decompression classes

Rate me:
Please Sign up or sign in to vote.
4.89/5 (73 votes)
11 Feb 2014CPOL36 min read 189.1K   12.4K   246  
This project will give you the tools needed to compress and decompress files using the Deflate method of compression, and to read and write standard Zip files.
/////////////////////////////////////////////////////////////////////
//
//	UZipDotNet
//	ZIP File processing
//
//	AddFilesAndFoldersForm.cs
//	Windows form allowing the user to select files and folders
//	to be included in the ZIP archive.
//
//	Granotech Limited
//	Author: Uzi Granot
//	Version 1.0
//	March 30, 2012
//	Copyright (C) 2012 Granotech Limited. All Rights Reserved
//
//	Version 1.2 October 23, 2012
//		AddFilesAndFoldersForm.cs
//			Method: OnAddButton
//			Fix problen related to creating zip file of a folder at the root of the drive
//
//	UZipDotNet application is a free software.
//	It is distributed under the Code Project Open License (CPOL).
//	The document UZipDotNetReadmeAndLicense.pdf contained within
//	the distribution specify the license agreement and other
//	conditions and notes. You must read this document and agree
//	with the conditions specified in order to use this software.
//
/////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
using System.Drawing;

namespace UZipDotNet
{
public partial class AddFilesAndFoldersForm : Form
	{
	////////////////////////////////////////////////////////////////////
	//	Members
	////////////////////////////////////////////////////////////////////

	public  List<FileHeader>	AddDir;
	public  String				RootDir;
	public  Int32				RootDirPtr;
	public	Int32				CompLevel;

	private FileSystemWatcher	DirWatcher;
	private Timer				FileListTimer;
	private Boolean				RefreshFileList;
	private Boolean				ResizeLock;

	////////////////////////////////////////////////////////////////////
	//	Constructor
	////////////////////////////////////////////////////////////////////

	public AddFilesAndFoldersForm()
		{
		InitializeComponent();
		}

	////////////////////////////////////////////////////////////////////
	//	On Load
	////////////////////////////////////////////////////////////////////

	private void OnLoad
			(
			object sender,
			EventArgs e
			)
		{
		// program initialization
		ProgramInitialization();

		// Directory tree initialization
		DirectoryTreeInit();

		// resize
		OnResize(this, null);
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	One time program initialization
	////////////////////////////////////////////////////////////////////

	private void ProgramInitialization()
		{
		// create image list
		DirectoryTree.ImageList = new ImageList();
		DirectoryTree.ImageList.Images.Add(Properties.Resources.MyComputerBmp);
		DirectoryTree.ImageList.Images.Add(Properties.Resources.MyComputerSelBmp);
		DirectoryTree.ImageList.Images.Add(Properties.Resources.DiskDriveBmp);
		DirectoryTree.ImageList.Images.Add(Properties.Resources.DiskDriveSelBmp);
		DirectoryTree.ImageList.Images.Add(Properties.Resources.FolderBmp);
		DirectoryTree.ImageList.Images.Add(Properties.Resources.FolderSelBmp);
		DirectoryTree.ImageList.Images.Add(Properties.Resources.FileBmp);

		// attach the same list to the file tree
		FileList.SmallImageList = DirectoryTree.ImageList;

		// other options
		DirectoryTree.ShowLines = true;
		DirectoryTree.ShowRootLines = false;
		DirectoryTree.Scrollable = true;
		DirectoryTree.BorderStyle = BorderStyle.FixedSingle;
		DirectoryTree.ShowPlusMinus = true;

		// create a new FileSystemWatcher
		DirWatcher = new FileSystemWatcher();

		// watch for changes in LastAccess and LastWrite times, and renaming of files or directories
		DirWatcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.LastWrite |
			NotifyFilters.Size | NotifyFilters.FileName | NotifyFilters.DirectoryName;

		// Add event handlers.
		DirWatcher.Changed += new FileSystemEventHandler(OnDirWatcherEvent);
		DirWatcher.Created += new FileSystemEventHandler(OnDirWatcherEvent);
		DirWatcher.Deleted += new FileSystemEventHandler(OnDirWatcherEvent);
		DirWatcher.Renamed += new RenamedEventHandler(OnDirWatcherEvent);

		// create file list timer
		FileListTimer = new Timer();
		FileListTimer.Tick += new EventHandler(OnFileListTimer);
		FileListTimer.Interval = 500;

		// exit
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	Build initial tree. My computer plus drives
	////////////////////////////////////////////////////////////////////

	private void DirectoryTreeInit()
		{
		// root node
		TreeNode RootNode = new TreeNode();
		RootNode.ImageIndex = (Int32) DispIcon.MyComputer;
		RootNode.SelectedImageIndex = (Int32) DispIcon.MyComputerSel;
		RootNode.Text = "My Computer";
		RootNode.Tag = String.Empty;
		DirectoryTree.Nodes.Add(RootNode);

		// drives child nodes
		DriveInfo[] Drives = DriveInfo.GetDrives();
        foreach(DriveInfo DI in Drives)
			{
			// ignore CD/DVD and drives not ready
			if(!DI.IsReady || DI.DriveType == DriveType.CDRom) continue;

			// build node
			TreeNode DriveNode = new TreeNode();
			DriveNode.ImageIndex = (Int32) DispIcon.DiskDrive;
			DriveNode.SelectedImageIndex = (Int32) DispIcon.DiskDriveSel;
			DriveNode.Text = String.Format("{0} [{1}]", String.IsNullOrEmpty(DI.VolumeLabel) ? "Drive" : DI.VolumeLabel, DI.Name);
			DriveNode.Tag = DI.RootDirectory.FullName;
			RootNode.Nodes.Add(DriveNode);

			// add one dummy child node
			DriveNode.Nodes.Add(new TreeNode());
			}

		// expend first level
		RootNode.Expand();

		// exit
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	User double click a node in order to expand it
	////////////////////////////////////////////////////////////////////

	private void OnBeforeExpand
			(
			Object sender,
			TreeViewCancelEventArgs e
			)
		{
		// do not expand if there are no subdirectories
		if(AddSubdirectories(e.Node)) e.Cancel = true;

		// exit
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	Add one level of child nodes
	////////////////////////////////////////////////////////////////////

	private Boolean AddSubdirectories
			(
			TreeNode	ParentNode
			)
		{
		// nothing to do if we have subdirectories
		if(ParentNode.Nodes[0].Tag != null) return(false);

		// remove the existing dummy record
		ParentNode.Nodes.RemoveAt(0);

		// directory info for current node
		DirectoryInfo DirInfo = new DirectoryInfo((String) ParentNode.Tag);

		// loop for all sub-directories
        foreach(DirectoryInfo Dir in DirInfo.GetDirectories())
			{
			// test for two special cases
			if(IsSpecialDirectory(Dir.Name)) continue;

			// build node
			TreeNode ChildNode = new TreeNode();
			ChildNode.ImageIndex = (Int32) DispIcon.Folder;
			ChildNode.SelectedImageIndex = (Int32) DispIcon.FolderSel;
			ChildNode.Text = Dir.Name;
			ChildNode.Tag = Dir.FullName;
			ParentNode.Nodes.Add(ChildNode);

			// add dummy grandchild record
			ChildNode.Nodes.Add(new TreeNode());
			}

		// exit
		return(ParentNode.Nodes.Count == 0);
		}

	////////////////////////////////////////////////////////////////////
	//	Directory watcher detected a change
	////////////////////////////////////////////////////////////////////

	void OnDirWatcherEvent
			(
			object sender,
			FileSystemEventArgs e
			)
		{
		// set the refresh request
		RefreshFileList = true;
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	Simulate after select directory event
	////////////////////////////////////////////////////////////////////

	void OnFileListTimer
			(
			object sender,
			EventArgs e
			)
		{
		if(RefreshFileList) PopulateFileListView(DirectoryTree.SelectedNode);
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	On after select directory
	////////////////////////////////////////////////////////////////////

	private void OnAfterSelectDirectory
			(
			object sender,
			TreeViewEventArgs e
			)
		{
		PopulateFileListView(e.Node);
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	Populate File List View
	////////////////////////////////////////////////////////////////////

	private void PopulateFileListView
			(
			TreeNode ParentNode
			)
		{
		// stop the timer
		FileListTimer.Stop();

		// stop the directory watch program
		DirWatcher.EnableRaisingEvents = false;

		// reset the refresh flag
		RefreshFileList = false;

		// clear file list
		// this routine will call resize if the file list area has scroll bars
		FileList.Items.Clear();

		// root directory
		if(ParentNode.Level == 0)
			{
			// drives child nodes
			foreach(DriveInfo Drv in DriveInfo.GetDrives())
				{
				// ignore CD/DVD and drives not ready
				if(!Drv.IsReady || Drv.DriveType == DriveType.CDRom) continue;

				// add disk drive to file list
				ListViewItem Item = new ListViewItem(
					String.Format("{0} [{1}]", String.IsNullOrEmpty(Drv.VolumeLabel) ? "Drive" : Drv.VolumeLabel, Drv.Name),
					(Int32) DispIcon.DiskDrive);
				Item.Tag = Drv;
				FileList.Items.Add(Item);
				}
			}

		// not root
		else
			{
			// directory info for current node
			DirectoryInfo DirInfo = new DirectoryInfo((String) ParentNode.Tag);

			// loop for all subdirectories
			foreach(DirectoryInfo Dir in DirInfo.GetDirectories())
				{
				// test for special cases
				if(IsSpecialDirectory(Dir.Name)) continue;

				// directory item
				ListViewItem Item = new ListViewItem(Dir.Name, (Int32) DispIcon.Folder);
				Item.Tag = Dir;
				Item.SubItems.Add(FormatFileDateTime(Dir.LastAccessTime));
				Item.SubItems.Add(FormatFileAttributes(Dir.Attributes));
				FileList.Items.Add(Item);
				}

			// loop for all files
			foreach(FileInfo File in DirInfo.GetFiles())
				{
				// file item
				ListViewItem Item = new ListViewItem(File.Name, (Int32) DispIcon.File);
				Item.Tag = File;
				Item.SubItems.Add(FormatFileDateTime(File.LastAccessTime));
				Item.SubItems.Add(FormatFileAttributes(File.Attributes));
				Item.SubItems.Add(FormatFileSize(File.Length));
				FileList.Items.Add(Item);
				}

			// start the directory watch program
			DirWatcher.Path = (String) ParentNode.Tag;
			DirWatcher.EnableRaisingEvents = true;

			// start the timer
			FileListTimer.Start();
			}

		// adjust columns
		OnFileListResize(null, null);
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	User double click a directory within file list
	////////////////////////////////////////////////////////////////////

	private void OnFileDoubleClick
			(
			object sender,
			EventArgs e
			)
		{
		// get selected list
		ListView.SelectedListViewItemCollection SelectedList = FileList.SelectedItems;

		// for double click only one is acceptable
		if(SelectedList.Count != 1) return;

		// get selected item
		ListViewItem Item = SelectedList[0];

		// path
		String Path;

		// double click on a directory
		if(Item.Tag.GetType() == typeof(DirectoryInfo))
			{
			Path = ((DirectoryInfo) Item.Tag).FullName;
			}

		// double click on a drive
		else if(Item.Tag.GetType() == typeof(DriveInfo))
			{
			Path = ((DriveInfo) Item.Tag).Name;
			}

		// double click on a file
		else
			{
			return;
			}

		// current selected node in view tree
		if(!DirectoryTree.SelectedNode.IsExpanded) DirectoryTree.SelectedNode.Expand();

		// search
		for(TreeNode ChildNode = DirectoryTree.SelectedNode.FirstNode; ChildNode != null; ChildNode = ChildNode.NextNode)
			{
			if((String) ChildNode.Tag == Path)
				{
				// NOTE: changing the selected node will activate OnAfterSelectDirectory()
				DirectoryTree.SelectedNode = ChildNode;
				break;
				}
			}

		// exit
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	Select all Ctrl-A
	////////////////////////////////////////////////////////////////////

	private void OnKeyDown(object sender, KeyEventArgs e)
		{
		if(FileList.Items.Count != 0 && e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.A)
			{
			foreach(ListViewItem LVI in FileList.Items) LVI.Selected = true;
			}
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	On add button
	////////////////////////////////////////////////////////////////////

	private void OnAddButton
			(
			object sender,
			EventArgs e
			)
		{
		// get selected list
		ListView.SelectedListViewItemCollection SelectedList = FileList.SelectedItems;

		// the list is empty
		if(SelectedList.Count == 0) return;

		// trap duplicate file errors
		try
			{
			// zip file root directory
			RootDir = (String) DirectoryTree.SelectedNode.Tag;
			// Version 1.2 fix
			RootDirPtr = RootDir.Length;
			if(RootDirPtr != 3 || !Char.IsLetter(RootDir[0]) || RootDir[1] != ':' || RootDir[2] != '\\') RootDirPtr++;

			// create empty file list
			AddDir = new List<FileHeader>();

			// get all selected directories
			foreach(ListViewItem Item in SelectedList)
				{
				if(Item.Tag.GetType() == typeof(DirectoryInfo)) ProcessDirectory((DirectoryInfo) Item.Tag);
				}

			// get all selected files in the zip file root directory
			foreach(ListViewItem Item in SelectedList)
				{
				if(Item.Tag.GetType() == typeof(FileInfo))
					{
					// shortcut for file information
					FileInfo FI = (FileInfo) Item.Tag;

					// create zip directory file header record with name date time and attributes
					FileHeader FH = new FileHeader(FI.FullName.Substring(RootDirPtr), FI.LastWriteTime, FI.Attributes, 0, FI.Length);

					// find item poition in the list
					Int32 Index = AddDir.BinarySearch(FH);
					if(Index >= 0) throw new ApplicationException("Duplicate file name");

					// add to the list
					AddDir.Insert(~Index, FH);
					}
				}

			// save compression level
			CompLevel = (Int32) CompLevelUpDown.Value;

			// close screen
			DialogResult = DialogResult.OK;
			return;
			}

		// error
		catch(Exception Ex)
			{
			// error exit
			String[] ExceptionStack = ExceptionReport.GetMessageAndStack(this, Ex);
			MessageBox.Show(this, "Add files and folders error\n" + ExceptionStack[0] + "\n" + ExceptionStack[1],
				"Add Files Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			return;
			}
		}

	////////////////////////////////////////////////////////////////////
	//	Process selected directory
	////////////////////////////////////////////////////////////////////

	private void ProcessDirectory
			(
			DirectoryInfo	DirInfo
			)
		{
		// process all subdirectories
		foreach(DirectoryInfo Dir in DirInfo.GetDirectories()) ProcessDirectory(Dir);

		// process all files in this directory
		foreach(FileInfo FI in DirInfo.GetFiles())
			{
			// create zip directory file header record with name date time and attributes
			FileHeader FH = new FileHeader(FI.FullName.Substring(RootDirPtr), FI.LastWriteTime, FI.Attributes, 0, FI.Length);

			// find item poition in the list
			Int32 Index = AddDir.BinarySearch(FH);
			if(Index >= 0) throw new ApplicationException("Duplicate file name");

			// add to the list
			AddDir.Insert(~Index, FH);
			}

		// add the directory name to the list
		// create zip directory file header record with name date time and attributes
		FileHeader DH = new FileHeader(DirInfo.FullName.Substring(RootDirPtr) + "\\", DirInfo.LastWriteTime, DirInfo.Attributes, 0, 0);

		// find item poition in the list
		Int32 DirIndex = AddDir.BinarySearch(DH);
		if(DirIndex >= 0) throw new ApplicationException("Duplicate directory name");

		// add to the list
		AddDir.Insert(~DirIndex, DH);

		// exit
		return;
		}

	////////////////////////////////////////////////////////////////////
	// Test for special system directories
	////////////////////////////////////////////////////////////////////

	private Boolean IsSpecialDirectory
			(
			String	Name
			)
		{
		// test for two special cases
		if(String.Compare(Name, "$Recycle.Bin", true) == 0) return(true);
		if(String.Compare(Name, "System Volume Information", true) == 0) return(true);
		return(false);
		}

	////////////////////////////////////////////////////////////////////
	// Format file size
	////////////////////////////////////////////////////////////////////

	private static String FormatFileSize
			(
			Int64		Size
			)
		{
		return(String.Format("{0:#,##0} KB", (Size + 999) / 1000));
		}

	////////////////////////////////////////////////////////////////////
	// Format file date and time
	////////////////////////////////////////////////////////////////////

	private static String FormatFileDateTime
			(
			DateTime	FileDate
			)
		{
		return(String.Format("{0:yyyy}/{0:MM}/{0:dd} {0:HH}:{0:mm}:{0:ss}", FileDate));
		}

	////////////////////////////////////////////////////////////////////
	// Format file attributes
	////////////////////////////////////////////////////////////////////

	private static String FormatFileAttributes
			(
			FileAttributes Attr
			)
		{
		return(String.Format("{0}{1}{2}",
			(Attr & FileAttributes.System) != 0 ? "S" : "",
			(Attr & FileAttributes.Hidden) != 0 ? "H" : "",
			(Attr & FileAttributes.ReadOnly) != 0 ? "R" : ""));
		}

	////////////////////////////////////////////////////////////////////
	//	On Resize
	////////////////////////////////////////////////////////////////////

	private void OnResize
			(
			object sender,
			EventArgs e
			)
		{
		// protect against minimize button
		if(ClientSize.Width == 0) return;

		// button group
		ButtonsGroupBox.Left = (ClientSize.Width - ButtonsGroupBox.Width) / 2;
		ButtonsGroupBox.Top = ClientSize.Height - ButtonsGroupBox.Height;

		// split container
		FileArea.Left = 0;
		FileArea.Width = ClientSize.Width;
		FileArea.Top = 0;
		FileArea.Height = ButtonsGroupBox.Top;

		// exit
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	On Resize
	////////////////////////////////////////////////////////////////////

	private void OnFileAreaResize
			(
			object sender,
			EventArgs e
			)
		{
		// change splitter line
		FileArea.SplitterDistance = FileArea.Width / 3;

		// exit
		return;
		}

	////////////////////////////////////////////////////////////////////
	// Resize panel 1
	////////////////////////////////////////////////////////////////////

	private void OnFileAreaPanel1Resize
			(
			object sender,
			EventArgs e
			)
		{
		DirectoryTree.Left = 0;
		DirectoryTree.Width = FileArea.Panel1.Width - 1;
		DirectoryTree.Top = 0;
		DirectoryTree.Height = FileArea.Panel1.Height;
		return;
		}

	////////////////////////////////////////////////////////////////////
	// Resize panel 2
	////////////////////////////////////////////////////////////////////

	private void OnFileAreaPanel2Resize
			(
			object sender,
			EventArgs e
			)
		{
		FileList.Left = 1;
		FileList.Width = FileArea.Panel2.Width - 1;
		FileList.Top = 0;
		FileList.Height = FileArea.Panel2.Height;
		return;
		}

	////////////////////////////////////////////////////////////////////
	// On resize file list
	////////////////////////////////////////////////////////////////////

	private void OnFileListResize
			(
			object sender,
			EventArgs e
			)
		{
		if(ResizeLock) return;
		ResizeLock = true;

		// get graphics object
		Graphics GR = CreateGraphics();

		// file list font
		Font ListFont = FileList.Font;

		// file name
		Int32 MinWidth = (Int32) Math.Ceiling(GR.MeasureString("File Name0000", ListFont).Width);

		// Date and time width
		FileList.Columns[1].Width = (Int32) Math.Ceiling(GR.MeasureString("2000/12/12 00:00:000", ListFont).Width);

		// attribute
		FileList.Columns[2].Width = (Int32) Math.Ceiling(GR.MeasureString("Attr.0", ListFont).Width);

		// size
		FileList.Columns[3].Width = (Int32) Math.Ceiling(GR.MeasureString("0,000,000 KB", ListFont).Width);

		// list is empty
		// Note: C# has a bug.
		// When FileList.Items.Clear() is executed, the system removes all items,
		// calls the resize routine while the count is unchanged.
		if(FileList.Items.Count == 0 || FileList.Items[0] == null)
			{
			FileList.Columns[0].Width = MinWidth;
			ResizeLock = false;
			return;
			}

		// check file name
		Int32 Space = FileList.ClientRectangle.Width - FileList.Columns[1].Width - FileList.Columns[2].Width - FileList.Columns[3].Width;
		if(Space < MinWidth) Space = MinWidth;
		FileList.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
		if(FileList.Columns[0].Width > Space) FileList.Columns[0].Width = Space;
		ResizeLock = false;
		return;
		}

	////////////////////////////////////////////////////////////////////
	//	On Closing
	////////////////////////////////////////////////////////////////////

	private void OnClosing
			(
			object sender,
			FormClosingEventArgs e
			)
		{
		return;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions