Creating and updating a zipfile - c#

Following the documentation, I'm having an extremely difficult time getting this to work. Using ZipFile I want to create a zip in memory and then be able to update it. On each successive call to update the, the zip reports that it has 0 entries.
What am I doing wrong?
public void AddFile(MemoryStream zipStream, Stream file, string fileName)
{
//First call to this zipStream is just an empty stream
var zip = ZipFile.Create(zipStream);
zip.BeginUpdate();
zip.Add(new ZipDataSource(file), fileName);
zip.CommitUpdate();
zip.IsStreamOwner = false;
zip.Close();
zipStream.Position = 0;
}
public Stream GetFile(Stream zipStream, string pathAndName)
{
var zip = ZipFile.Create(zipStream);
zip.IsStreamOwner = false;
foreach (ZipEntry hi in zip) //count is 0
{
}
var entry = zip.GetEntry(pathAndName);
return entry == null ? null : zip.GetInputStream(entry);
}
The custom data source
public class ZipDataSource : IStaticDataSource
{
private Stream _stream;
public ZipDataSource(Stream stream)
{
_stream = stream;
}
public Stream GetSource()
{
_stream.Position = 0;
return _stream;
}

ZipFile.Create(zipStream) is not just a convenient static accessor like anyone would think. If you're going to use that only use it for the very first time you're creating a zip. When opening up an existing zip you need to use var zip = new ZipFile(zipStream).
I've personally had many issues in the past with this library and would suggest that anyone looking for a good zip library to choose something other than icsharpziplib... The API just plain sucks.

Related

How can you benefit from the new System.Buffers (Span, Memory) when doing File I/O and parsing

I'm currently looking at the new System.Buffers with Span, ReadOnlySpan, Memory, ReadOnlyMemory, ...
I understand when passing e.g. a ReadOnlySpan (ROS) this could reduce heap allocations in many cases and make code perform better. Most examples regarding Span show you the .AsSpan(), .Slice(...) examples but that's it.
Once I have my data (e.g. byte[]) then I can create a Span or ReadOnlySpan from it and pass that to several methods/classes inside my library.
But how can File I/O be writting using System.Buffers (Span/Memory/..)?
I've tried to created two small (partial) examples to demonstrate the situation.
// Example 1:
using (var br = new BinaryReader(File.OpenRead(pathToFile)) {
ReadFile(br);
}
private void ReadFile(BinaryReader br) {
ParseHeader(...);
}
private void ParseHeader(BinaryReader br) {
br.ReadBytes(...);
br.ReadInt32();
// ...
}
and
// Example 2:
public Foo GetFileAsFoo(string path) {
using (var s = new FileStream(path, FileMode.Open, FileAccess.Read) {
return ReadAndGetFoo(s);
}
}
public Foo ReadAndGetFoo(Stream file) {
// copy to memorystream as filestream file I/O is slow
var ms = new MemoryStream();
file.CopyTo(ms);
ms.Position = 0;
Parser p = new Parser(ms);
p.Read();
return p.GetFoo();
}
public class Parser {
private readonly Stream _s;
public Parser(Stream stream) {
_s = stream;
}
int Peek() {
if (_s.Position >= _s.Length) return -1;
int r = _s.ReadByte();
_s.Seek(-1, SeekOrigin.Current);
return r;
}
public void Read() {
// logic here
}
public Foo GetFoo() {
// ...
return _Foo;
}
// other methods to parse
}
The question for the first example is mainly on how can I get a ReadOnlySpan/Memory/...(?) from:
using (var br = new BinaryReader(File.OpenRead(pathToFile))
I'm aware of System.Buffers.Binary.BinaryPrimites to replace the BinaryReader, but this requires a ReadOnlySpan. How would I get my data from File.OpenRead as span in the first place, similar as I do with the BinaryReader?
What options are available?
I guess there is no class in the BinaryPrimities that keeps track of the 'position' similar as a BinaryReader does?
https://learn.microsoft.com/en-us/dotnet/api/system.buffers.binary.binaryprimitives?view=net-5.0
The second example instead of working with a BinaryReader is working on a Stream.
To keep it efficient, I first do a copy to a memorystream to reduce the I/O. (I know this one-time copy is slow and should be avoided).
How could this File as Stream be read the using System.Buffers (Span/Memory/...?) ?
(byte per byte is being read and parsed using Stream.ReadByte())
Hope to learn something!

MemoryStream Disposed after using Workbook.Save() method OpenXML

I have been messing with this for hours to no avail. I am trying to copy an excel file, add a new sheet to it, put the file in a MemoryStream and then return the stream.
Here is the code:
public Stream ProcessDocument()
{
var resultStream = new MemoryStream();
string sourcePath = "path\\to\\file";
string destinationPath = "path\\to\\file";
CopyFile(destinationPath, sourcePath);
var copiedFile = SpreadsheetDocument.Open(destinationPath, true);
var fileWithSheets = SpreadsheetDocument.Open("path\\to\\file", false);
AddCopyOfSheet(fileWithSheets.WorkbookPart, copiedFile.WorkbookPart, "foo");
using(var stream = new MemoryStream()){
copiedFile.WorkbookPart.Workbook.Save(stream);
stream.Position = 0;
stream.CopyTo(resultsStream);
}
return resultsStream;
}
public void CopyFile(string outputFullFilePath, string inputFileFullPath)
{
int bufferSize = 1024 * 1024;
using (var fileStream = new FileStream(outputFullFilePath, FileMode.OpenOrCreate))
{
var fs = new FileStream(inputFileFullPath, FileMode.Open, FileAccess.ReadWrite);
fileStream.SetLength(fs.Length);
int bytesRead = -1;
byte[] bytes = new byte[bufferSize];
while ((bytesRead = fs.Read(bytes, 0, bufferSize)) > 0)
{
fileStream.Write(bytes, 0, bytesRead);
}
fs.Close();
fileStream.Close();
}
}
public static void AddCopyOfSheet(WorkbookPart sourceDocument, WorkbookPart destinationDocument, string sheetName)
{
WorksheetPart sourceSheetPart = GetWorkSheetPart(sourceDocument, sheetName);
destinationDocument.AddPart(sourceSheetPart);
}
public static WorksheetPart GetWorksheetPart(WorkbookPart workbookPart, string sheetName)
{
string id = workbookPart.Workbook.Descendants<Sheet>().First(x => x.Name.Value.Contains(sheetName)).Id
return (WorksheetPart)workbookPart.GetPartById(id);
}
The issue seems to arise from copiedFile.WorkbookPart.Workbook.Save(stream).
After this is ran, I get an error saying that there was an exception of type 'System.ObjectDisposedException'. The file copies fine and adding the sheet seems to also be working.
Here's what I've tried:
Using .Save() without stream as a parameter. It does nothing.
Using two different streams (hence the resultStream jank left in this code)
Going pure OpenXML and copying the WorkbookParts to a stream directly. Tested with a plain text excel and was fine, but it breaks the desired file because it has some advanced formatting that does not seem to work well with OpenXML. I am open to refactoring if someone knows how I could work around this, though.
What I haven't tried:
Creating ANOTHER copy of the copy and using the SpreadsheetDocument.Create(stream, type) method. I have a feeling this would work but it seems like an awful and slow solution.
Updating OpenXML. I am currently on 2.5.
Any feedback or ideas are hugely appreciated. Thank you!
PS: My dev box is airgapped so I had to hand write this code over. Sorry if there are any errors.
Turns out that copiedFile.WorkbookPart.Workbook.Save(stream); disposes of the stream by default. The workaround to this was to make a MemoryStream class that overloads its ability to be disposed, like so:
public class DisposeLockableMemoryStream : MemoryStream
{
public DisposeLockableMemoryStream(bool allowDispose)
{
AllowDispose = allowDispose;
}
public bool AllowDispose { get; set; }
protected override void Dispose(bool disposing)
{
if (!AllowDispose)
return;
base.Dispose(disposing);
}
}
All you need to do is make sure you stream.AllowDispose = true and then dispose of it once you're done.
Now, this didn't really fix my code because it turns out that .Save() only tracks changes made to the document, not the entire thing!!!. Basically, this library is hot garbage and I regret signing up for this story to begin with.
For more information, see a post I made on r/csharp.

How to read and sort a text file

How do i read and sort a text file
sorry if this is an easy question I'm new to coding. I've tried many online solution but none seems to fix my problem:
namespace Login_but_it_hopefully_works
{
public partial class Leaderboard : Form
{
string Line = "";
private string filepath1 = #"Compdetails.txt";
FileStream readerStream = new FileStream("Compdetails.txt", FileMode.Open);
string[] content = null;
public Leaderboard()
{
InitializeComponent();
}
public object ListReadFile { get; private set; }
private void bttn_load_Click(object sender, EventArgs e)
{
string[] content = null;
//Read the content
using (StreamReader CompTXT = File.OpenText(filepath1))
{
content = CompTXT.ReadToEnd().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
//Remove the entries in the file
readerStream.SetLength(0);
}
FileStream writerStream = new FileStream(#"Desktop\Source\text.txt", FileMode.OpenOrCreate);
using (StreamWriter writer = new StreamWriter(writerStream))
{
//Sort the content and write back to the same file
Array.Sort(content);
writer.Write(string.Join(Environment.NewLine, content));
}
}
}
}
The error is:
Additional information: The process cannot access the file
'E:\CS\Login\Login but it hopefully works\bin\Debug\Compdetails.txt'
because it is being used by another process and the line is " using
(StreamReader CompTXT = File.OpenText(filepath3))"
Remove the 2 lines involving readerStream. They are not accomplishing what do you think they are, but they are causing that error. :-) Your next task will be to overwrite the file rather than append to it.
To elaborate on the cause of the error: having that field declared in the class and initialized by opening a stream causes the file to be locked for as long as an instance of the class exists. When you then call the button event method and try to open another stream with another lock on the same file, an exception results.

C# WebApi How can i get complete Image from HttpPostedFile

Here I'm using WebApi I want an image for sending Email For that I wrote the code as:
var files = HttpContext.Current.Request.Files;
if (files.Count > 0) {
for (int i = 0; i < files.Count; i++) {
HttpPostedFile file = files[i];
mailModel.filename = file.FileName;
mailModel.filecontent = file.InputStream;
}
}
Here How can i Bind mailModel.Filecontent
My Class File as
public class SendMailRequest
{
public string filecontent { get; set; }
public string filename { get; set; }
}
My Mail Sending Code is:
if (mailModel.filename != null) {
string tempPath = WebConfigurationManager.AppSettings["TempFile"];
string filePath = Path.Combine(tempPath, mailModel.filename);
using(System.IO.FileStream reader = System.IO.File.Create(filePath)) {
byte[] buffer = Convert.FromBase64String(mailModel.filecontent);
reader.Write(buffer, 0, buffer.Length);
reader.Dispose();
}
msg.Attachments.Add(new Attachment(filePath));
How can I Bind my File to the FileContent?
I think you probably want to learn about using Streams in .Net? First use Stream not string here:
public class SendMailRequest
{
public Stream FileContent { get; set; }
public string FileName { get; set; }
}
Then, because it's utterly confusing, rename your reader to writer.
Then, don't do anything stringy with your Stream, just do:
await mailModel.filecontent.CopyToAsync(writer);
There is a complication here. This code assumes that the original uploaded filestream is still present and working, in memory, at the time that you try to send your email. Whether that is true depends on what happens in between.
Especially, if the Http request processing has finished and a response been returned before the email gets sent, the filecontent stream has probably already gone away. has A safer course is to do the copy straight away in the controller:
file.InputStream.CopyToASync(mailModel.filecontent)
but at this point I have to say, I would rather either (1) copy straight to a file or (2) copy into a MemoryStream. i.e.
mailModel.filecontent= new MemoryStream();
file.InputStream.CopyToASync(mailModel.filecontent)
(If you use MemoryStream, you must calculate what is the largest file you are willing to handle, and make sure bigger files are rejected before you create the memory stream).
Finally, if this populates your file with Base64 instead of the binary, look at the answers to this question: HttpRequest files is empty when posting through HttpClient

C# .NET Why Stream.Seek is required when unzipping stream

I'm working on a project where I need the ability to unzip streams and byte arrays as well as zip them. I was running some unit tests that create the Zip from a stream and then unzip them and when I unzip them, the only way that DonNetZip sees them as a zip is if I run streamToZip.Seek(o,SeekOrigin.Begin) and streamToZip.Flush(). If I don't do this, I get the error "Cannot read Block, No data" on the ZipFile.Read(stream).
I was wondering if anyone could explain why that is. I've seen a few articles on using it to actually set the relative read position, but none that really explain why in this situation it is required.
Here is my Code:
Zipping the Object:
public Stream ZipObject(Stream data)
{
var output = new MemoryStream();
using (var zip = new ZipFile())
{
zip.AddEntry(Name, data);
zip.Save(output);
FlushStream(output);
ZippedItem = output;
}
return output;
}
Unzipping the Object:
public List<Stream> UnZipObject(Stream data)
{
***FlushStream(data); // This is what I had to add in to make it work***
using (var zip = ZipFile.Read(data))
{
foreach (var item in zip)
{
var newStream = new MemoryStream();
item.Extract(newStream);
UnZippedItems.Add(newStream);
}
}
return UnZippedItems;
}
Flush method I had to add:
private static void FlushStream(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
stream.Flush();
}
When you return output from ZipObject, that stream is at the end - you've just written the data. You need to "rewind" it so that the data can then be read. Imagine you had a video cassette, and had just recorded a program - you'd need to rewind it before you watched it, right? It's exactly the same here.
I would suggest doing this in ZipObject itself though - and I don't believe the Flush call is necessary. I'd personally use the Position property, too:
public Stream ZipObject(Stream data)
{
var output = new MemoryStream();
using (var zip = new ZipFile())
{
zip.AddEntry(Name, data);
zip.Save(output);
}
output.Position = 0;
return output;
}
When you write to a stream, the position is changed. If you want to decompress it (the same stream object), you'll need to reset the position. Else you'll get a EndOfStreamException because the ZipFile.Read will start at the stream.Position.
So
stream.Seek(0, SeekOrigin.Begin);
Or
stream.Position = 0;
would do the trick.
Offtopic but sure useful:
public IEnumerable<Stream> UnZipObject(Stream data)
{
using (var zip = ZipFile.Read(data))
{
foreach (var item in zip)
{
var newStream = new MemoryStream();
item.Extract(newStream);
newStream.Position = 0;
yield return newStream;
}
}
}
Won't unzip all items in memory (because of the MemoryStream used in the UnZipObject(), only when iterated. Thats because extracted items are yielded. (returning an IEnumerable<Stream>) More info on yield: http://msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx
Normally i wouldn't recomment returning data as stream, because the stream is something like an iterator (using the .Position as current position). This way it isn't by default threadsafe. I'd rather return these memory streams as ToArray().

Categories