Get progress for saving a zip file in C#? - c#

I have this method below, which when the user clicks the button, the program gets a list of files from a path, and zips them to a location (as long as the paths exist)
I have tested it, and it works well for small folders. When I get over 1gb, the gui was freezing. As a result, I started a new thread to stop that from happening. I tried various ways of getting the progress to display, but I get nothing.
If I manually close the program several minutes in, I get a various size temp file depending on how long I wait, so I know that it is writing the file, I just cant figure out how to tell the progress to show the user.
Any ideas?
Here is my method:
private void btnSyncJobs_Click(object sender, EventArgs e)
{
string startPath = #"J:\TV\Game Of Thrones";
string zipPath = #"j:\result.zip";
string sendPath = #"j:\";
if (Directory.Exists(startPath) && Directory.Exists(sendPath))
{
//MessageBox.Show("Correct","These 2 paths exist.");
if (File.Exists(zipPath))
{
File.Delete(zipPath); //delete existing file in order to save the new one
}
String[] allfiles = System.IO.Directory.GetFiles(startPath, "*.*", System.IO.SearchOption.AllDirectories);
int fileCount = allfiles.Length;
int filesAdded = 0;
double percentComplete = 0.00;
string fileCountString = Convert.ToString(fileCount);
MessageBox.Show("There are " + fileCountString + " files.","Count Notice.");
//create the new zip file
//ZipFile.CreateFromDirectory(startPath, zipPath, CompressionLevel.Fastest, true);
Task.Factory.StartNew(() =>
{
using (ZipFile zip = new ZipFile())
{
if (chkPassword.Checked)
{
zip.Password = txtPassword.Text;
}
foreach (string s in allfiles)
{
zip.AddItem(s);
//filesAdded++;//increment the count of files added
//percentComplete = filesAdded / fileCount;
//string percentLabel = filesAdded + " of "+ fileCount + " completed.";
//lblSyncJobsStatus.Text = percentLabel;
}
zip.Save(zipPath);
}
});
lblSyncJobsStatus.Text = "Completed successfully.";
}
else
{
MessageBox.Show("Error: One or more network drives are not attached.","Error");
lblSyncJobsStatus.Text = "Did not complete successfully.\n Please contact tech support.";
}
}
Just a note- I was testing in my tv folder to test on larger file sizes.
The line '//lblSyncJobsStatus.Text = percentLabel;' had to be commented out, because it can't update a value started in another thread. Even before that, I noticed that it was at 100% before the file was being written.

The ZipFile class does not appear to offer any events or callback opportunities to report progress.
If you're open to using the open source 7-Zip library instead (and the SevenZipSharp .NET wrapper), it looks like it provides a callback for reporting progress.
https://sevenzipsharp.codeplex.com/SourceControl/latest#SevenZip/ArchiveUpdateCallback.cs

Related

Zip files in C# in batches

I am using Zip Archive method to zip files in a folder. However, there is a requirement to read max 10 files from a folder at a particular time and zip it. If there are a large number of files say 100, then 10 zip folders need to be created using C#.
How do I achieve this?
I have tried this in Windows forms -
private void btnZip_Click(object sender, EventArgs e) {
string FolderPathToZip = txtFolderPath.Text.Trim();
//To create unique file name with date and time with nanoseconds.
string ZipFileName = "D:\\backup\\bak-" + DateTime.Now.ToString("ddMMyyyy-HHmmssfffff") + ".zip";
try {
//To check whether D:\Backup folder exists or not.
//If not exists this will create a BACKUP folder.
if (Directory.Exists("D:\\backup")) {} else {
Directory.CreateDirectory("D:\\backup");
}
//TO create a zip file.
ZipFile.CreateFromDirectory(FolderPathToZip, ZipFileName);
} catch (Exception) {
//If system throw any exception message box will display "SOME ERROR"
MessageBox.Show("Some Error");
}
//Display successfully created message to the user.
MessageBox.Show("Zip Filename : " + ZipFileName + " Created Successfully");
}
Psuedo Code:
Create a DirectoryInfo object for the Path where files exists
Use DirectoryInfo.GetFiles() to get the list of all files (*current)
In a loop (i=0; 10*i<fileList.Count; i++) do the following
subset = fileList.Skip(10 * i).Take(10);
Loop over this subset and create the Zip Archive
Be Happy :)

Speed up retrieve image into picturebox

I'm currently working on windows form application which allow user to retrieve image after searching at textbox. The problem is the image is load very slow. How can I overcome this problem to speed up the loading? If anyone has suggestions for a faster way to retrieve these images, it would be greatly appreciated. Here is my code:
string baseFolder = #"\\\\jun01\\hr\\photo";
string imgName = "*" + textBoxEmplNo.Text + "*.jpg";
//Bool to see if file is found after checking all
bool fileFound = false;
DirectoryInfo di = new DirectoryInfo(baseFolder);
foreach (var file in di.GetFiles(imgName, SearchOption.AllDirectories))
{
pictureBox1.Visible = true;
pictureBox1.Image = Image.FromFile(file.FullName);
fileFound = true;
break;
}
if (!fileFound)
{
pictureBox1.Visible = true;
pictureBox1.Image = Image.FromFile(#"\\\\jun01\\hr\\photo\\No-image-
found.jpg");
}
It's very likely here that the slowness you are experiencing is due to the wildcard search on all files in a remote directory, and/or potentially transferring a large file over the network for display in the PictureBox. You should time these two operations using a profiler (see for example https://www.simple-talk.com/dotnet/net-performance/the-why-and-how-of-net-profiling/) and consider optimizing the operations that take the most time.
Some possibilities might include pre-downloading all the images before the user makes a selection or downloading on a background thread while displaying a "Please wait" message on the UI.
It could be the case that the code slowness is due to the wildcard search on all files: (e.g. di.GetFiles takes too long)
Too many files in your top folder/subfolders.
The network connection is too slow to fetch file information or photos are too big. (remote directory access could be slow - you can check by copying a big file say 1GB to your remote directory and copy back to your PC)
The photos may be stored with different aspect ratios to your picture box size. (Rendering takes a while if resizing needs to happen)
For starters, let us assume your folder has too many files (1000s), then we need to do a smarter search for the first file folder by folder:
string baseFolder = #"\\\\jun01\\hr\\photo";
string imgName = "*" + textBoxEmplNo.Text + "*.jpg";
var file = FindFirstFile(baseFolder, imgName);
if (!string.IsNullorEmpty(file))
{
pictureBox1.Visible = true;
pictureBox1.Image = Image.FromFile(file);
}
else
{
pictureBox1.Visible = true;
pictureBox1.Image = Image.FromFile(#"\\\\jun01\\hr\\photo\\No-image-found.jpg");
}
where FindFirstFile is taken from Markus's answer as:
public static string FindFirstFile(string path, string searchPattern)
{
string[] files;
try
{
// Exception could occur due to insufficient permission.
files = Directory.GetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
}
catch (Exception)
{
return string.Empty;
}
if (files.Length > 0)
{
return files[0];
}
else
{
// Otherwise find all directories.
string[] directories;
try
{
// Exception could occur due to insufficient permission.
directories = Directory.GetDirectories(path);
}
catch (Exception)
{
return string.Empty;
}
// Iterate through each directory and call the method recursivly.
foreach (string directory in directories)
{
string file = FindFirstFile(directory, searchPattern);
// If we found a file, return it (and break the recursion).
if (file != string.Empty)
{
return file;
}
}
}
return string.Empty;
}

Outputing to .txt file

I am a little stuck. I have to use the getItems method and output it to PrintItems.txt, but I am not sure how to appoach this problem.
This is my GetItems method:
public string getItems()
{
string strout = "stock items " + "\n";
foreach (Stock s in stock)
{
strout = strout + s.ToString() + "\n";
}
return strout;
}
This is my PrintItems method:
string filename = "printitems.txt";
int count = 0;
using (StreamWriter sw = new StreamWriter(filename))
{
while (count < 5 && count < stock.Count)
{
Stock t = stock[count];
sw.WriteLine(t);
count++;
}
}
It doesn't work because it doesn't write to a file at all.
You code generally should work.
But since you haven't specified full path to the text file - it will be created in the same folder where your executable file is.
If you running it from Visual Studio - it should be in your_project\bin\Debug or your_project\bin\Release folder.
You could use:
File.WriteAllText(#"C:\dummy.txt", strout);
(As long as you don't expect the string to be massive - i.e. 10MB)
'File' uses System.IO
If you made no copy & paste error then there are 4 possibilities for your pheonomenon:
string filename = "printitems.txt";
int count = 0;
using (StreamWriter sw = new StreamWriter(filename))
{
while (count < 5 && count < stock.Count)
{
Stock t = stock[count];
sw.WriteLine(t.ToString());
count++;
}
}
You had no { after the streamwriter using line was something I saw first. The second thing is that Stock t normally can't be written directly but instead you need to do the same thing as when printing it out to the console.
Third: your code does not say anything about if stock is filled or not.
Fourth: The file: You should specify a directory (not only the filename) as else it can be that it is tried to create the file in a location where you have no permissions to create a file in (normally if you put no additional path info in the same path as where the application runs in is used [if you start from the visual studio itself then the appropraite bin path] as location where the file would be created).
Additionally: You should put a try catch block around the whole thing as there can be unexpected phenomenons which result in an exception.

Upload form not working - File is being used by another process

I've got a C# file upload that is meant to extract XML tags from inside a DocX document, the problem I'm facing is that when the file is uploaded, the error "File is being used by another process" comes up. Attempting to delete the document shows it is being used by IIS process manager.
Is there a way to stop my code to get it to continue running?
<script runat="server">
//foreach (DataRow row in table.Rows)
//{
// string dbColumnNames = (selectedData.ToString());
//send files
//}
public string _TempFileLocation = ""; //Used to locate Word Document File Path
//THE USER UPLOAD CONTROL. users use this to upload the document to the server
public void XMLextractor(string _filePath)
{
//XML extraction code
displayFilepath.Text = _filePath;
_TempFileLocation = _filePath;
}
//names the script manager which will be used when the user attempts to upload a form / gives an error if they incorrectly attempt to upload
protected void UploadButton_Click(object sender, EventArgs e)
{
//if file is located
if (FileUploadControl.HasFile)
{
try
{
//allow content type of document / docx
if (FileUploadControl.PostedFile.ContentType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
{
if (FileUploadControl.PostedFile.ContentLength < 10485760) // 10mb)
{
//name the filename, find the path of the name
string filename = Path.GetFileName(FileUploadControl.FileName);
//path of server upload (we just need to save it as a variable to be found on the next page, as it will be made / deleted
FileUploadControl.SaveAs(Server.MapPath("~/") + filename);
//update the label with file uploaded
StatusLabel.Text = "Upload status: File uploaded!";
XMLextractor(Server.MapPath("~/") + filename);
//move onto template wizard page
//Response.Redirect("http://portal.acoura.com/admin/templatewizard.aspx", false);
WordprocessingDocument _TempDoc = WordprocessingDocument.Open(Server.MapPath("~/") + filename, true);
XDocument xdoc = XDocument.Load(Server.MapPath("~/") + filename);
//query to find particular descendants
var lv1s = from document in xdoc.Descendants("table")
select new
{
Header = document.Attribute("name").Value,
Children = document.Descendants("tag")
};
//Loop through results
StringBuilder result = new StringBuilder();
foreach (var lv1 in lv1s)
{
result.AppendLine(lv1.Header);
foreach (var lv2 in lv1.Children)
result.AppendLine(" " + lv2.Attribute("name").Value);
}
//the label should contain the content controls of the document, using the class, XMLfromDocument
labelContentControls.Text = fileUpload_Displayx(XMLfromDocument.GetContentControls(_TempDoc));
}
else
//display the size the file needs to be less than
StatusLabel.Text = "Upload status: The file has to be less than 10mb!";
}
else
//tell the user only docx files are accepted
StatusLabel.Text = "Upload status: Only DOCX files are accepted!";
}
catch (Exception ex)
{
//display the exception message, in which case it would be either size / type / if it's present
StatusLabel.Text = "Upload status: The file could not be uploaded. The following error occured: " + ex.Message;
}
}
}
//needs to be replaced with the variable found in descendants / var tagContent
public string fileUpload_Displayx(XElement _contentcontrol)
{
string str = "";
str = _contentcontrol.Name.ToString();
return str;
}
//public static displayDatabase(object sender, EventArgs e)
// {
//}
//run the validate button on templatewizard, will mark up any problems or give green light
//if red, allow users to replace fields in the left column, from ones in the atabase on the right
//display upload button when validation is succesful. When Upload button runs, Take to new
// / existing page of reports, allow users to download this
</script>
You are opening the file without closing it on this line:
WordprocessingDocument _TempDoc = WordprocessingDocument.Open(Server.MapPath("~/") + filename, true);
Then you are opening it again with xDocument.Load():
XDocument xdoc = XDocument.Load(Server.MapPath("~/") + filename);
I assume that is where the error occurs.
If you handle all of the stuff XDocument needs to do first, and then open and close the WordProcessingDocument.Open() line to get the content controls, you should be fine.
Basically only one process can have a open and read or modify a file at a time, so if two operations from two different sources need to be performed, they must be performed sequentially on the file.
You can also open the file via a FileStream and then load the content into memory and into your XDocument, therefore negating the need to have the file opened twice by XDocument and WordProcessingDocument simultaneously.
Hope this helps!

How can I send the client multiple files to download

I'm using a handler(.ashx) to serve some files. I have a folder where I store ebooks. I name them by the books PK, and each book may have a few different formats:
211.html
211.pdf
211.prc
The following test code successfully downloads one book.
context.Response.ContentType = "application/octet-stream";
context.Response.AppendHeader("Content-Disposition", "attachment;filename=myfile.pdf");
context.Response.TransmitFile(context.Server.MapPath("~/Media/eBooks/212.pdf"));
How can I serve the client the three different formats? (The clients existing organization isn't in a folder)
I was trying to do something like this:
DirectoryInfo bookDir = new DirectoryInfo(context.Server.MapPath("~/Media/eBooks"));
FileInfo[] f = bookDir.GetFiles();
foreach (var n in f)
{
context.Response.AppendHeader("Content-Disposition", "attachment;filename=myfile.pdf");
context.Response.TransmitFile(context.Server.MapPath("~/Media/eBooks/212.pdf"));
}
But it downloads one file with no file extension.
The only way you can send multiple files in one response is to put them inside an archive package, e.g. a .zip file. That is at least something that can be done with code, using various tools (IIRC there's a zip packager inside the main .NET framework now; otherwise, SharpZipLib will do the job nicely).
To send multiple file to be downloaded, you should zip them using sharpziplib or other file zipping utility,files should be zipped and then download link can be send to the client to download them at once. the code below use ICSharpCode.SharpZipLib.dll Library.
You can call this class and pass your files which you want to zip.
public string Makezipfile(string[] files)
{
string[] filenames = new string[files.Length];
for (int i = 0; i < files.Length; i++)
filenames[i] = HttpContext.Current.Request.PhysicalApplicationPath + files[i].Replace(HttpContext.Current.Request.UrlReferrer.ToString(), "");
string DirectoryName = filenames[0].Remove(filenames[0].LastIndexOf('/'));
DirectoryName = DirectoryName.Substring(DirectoryName.LastIndexOf('/') + 1).Replace("\\", "");
try
{
string newFile = HttpContext.Current.Request.PhysicalApplicationPath + "your image directory\\" + DirectoryName + ".zip";
if (File.Exists(newFile))
File.Delete(newFile);
using (ZipFile zip = new ZipFile())
{
foreach (string file in filenames)
{
string newfileName = file.Replace("\\'", "'");
zip.CompressionLevel = 0;
zip.AddFile(newfileName, "");
}
zip.Save(newFile);
}
}
catch (Exception ex)
{
//Console.WriteLine("Exception during processing {0}", ex);
Response.Write(ex);
// No need to rethrow the exception as for our purposes its handled.
}
string a;
a = "your images/" + DirectoryName + ".zip";
return a;
}
I acknowledge the good Zip solutions mentioned here, but alternatively could you make 3 calls to the handler using javascript/XHR, requesting a different file format each time?
Admittedly, you are restricted by the number of concurrent requests supported by the browser, though I believe the browser will queue requests over the limit.
The benefit is that the User won't need to deal with a zip file, which may confuse them. Instead they should get 3 separate downloads.

Categories

Resources