Extract zip file to TStream using zlibar in Lazarus - delphi

I'm trying to extract a zip file from a TMemoryStream to another TMemoryStream using zlibar in Lazarus. From what I can tell, my code follows the examples found here. I am using a simple zip archive with one text file in it. The zip archive was created using PowerArchiver, nothing special. Here is my code:
uses
zlibar;
var
z, Dest: TMemoryStream;
unZip: TZLibReadArchive;
begin
z := TMemoryStream.Create;
z.LoadFromFile('kov.zip');
unZip := TZLibReadArchive.Create(z);
UnZip.ExtractFileToStream(0, Dest);
I am getting this error: "ZLibError(2) corrupt file or not a correct file type."
See zlibar.pas here: https://dl.dropbox.com/u/8899944/files/zlibar.pas
Any ideas why I am getting this error? Thanks.

The Zlibar library does not read zip files. It reads and writes a custom archive format. You can tell because the table-of-contents format described in zlibar.pas is completely different from the one used in zip files.
The FreePascalArchivePackage link looks like it might someday provide what you want, although the page last had significant changes in 2007.
There's also the ZipFile package, which appears to come with Lazarus.

Just a quick guess: Try to set z.Position := 0 before unZip := TZLibReadArchive.Create(z);.

Related

Delphi TZipFile extracts zero byte files

Using Delphi XE2 and the native TZipFile I attempt to extract the contents of a downloaded zip file (which contains 2 zipped XML files) and it always extracts zero byte files.
The file is being compressed by C# code like this:
var zipFile = new ZipFile();
foreach (Tuple<string, string> t in filesMeta) {
zipFile.AddFile(string.Format("{0}{1}", StaticVariables.WebServerFileStorage, t.Item2), "").FileName = t.Item1 + ".xml";
}
response.Clear();
response.ContentType = "application/zip";
zipFile.Save(response.OutputStream);
response.End();
The Delphi extraction code is this:
zipFile := TZipFile.Create;
try
filename := 'C:\test\57f52480-ec87-4169-a820-0a65bc4ad952.zip';
if zipFile.IsValid(filename) then begin
zipFile.Open(filename, zmRead);
zipFile.ExtractAll('C:\test\');
end;
finally
zipFile.Free;
end;
I even tried using a TStream as the source instead of a file on disk. That's actually what I want to do since the zip file is downloaded from a web server into a TStream. I tried to extract the data using the overloaded open method of TZipFile to open the stream.
That got me zero byte files so I saved the zip file to disk and tried to open the file from disk and extract. Same zero byte files are extracted.
I even tried using the class method to extract the files from the zip file on disk:
System.Zip.TZipFile.ExtractZipFile(filename, 'C:\Test\');
Same zero byte files extracted.
The zip file is valid and the 2 zipped XML files can be extracted properly by both Windows 7 native file handling and 7-Zip.
Now here is something nutty...
In desperation I tried to see what the ExtractToFile() procedure
David Heffernan came up with in this question about extracting a zip to a stream would do so I tried using it like this:
var x : integer;
var fileCount : integer;
var fileNames : TArray<string>;
begin
zipFile := TZipFile.Create;
try
filename := 'C:\test\57f52480-ec87-4169-a820-0a65bc4ad952.zip';
if zipFile.IsValid(filename) then begin
zipFile.Open(filename, zmRead);
fileCount := zipFile.FileCount;
fileNames := copy(zipFile.FileNames, 0, MaxInt);
zipFile.Close;
for x := 0 to fileCount-1 do begin
// Use David Heffernan's stream procedure
ExtractToFile(filename, x, 'C:\test\' + fileNames[x]);
end;
end;
finally
zipFile.Free;
end;
end;
And David's procedure extracts the files to disk as expected! WTF???
I am really confused why a convoluted method of extraction would work and the simple extraction method would not work. I'll use David's example if I have to but I'd prefer to get the normal extract working if possible.
Any ideas are appreciated.
Cheers!
TJ
I had this same problem.
The source code to TZipFile shows that the TStream passed into the Read function returns the entire zip file with position set to the start of the filename you're wanting. So don't rewind. Just copyfrom or do what you want with the TStream for the uncompressed length given in the TZipHeader.
ZipStream := TStream.Create;
ZipFile.Read(MyFileName, ZipStream, ZipHeader);
//leave ZipStream pointer where it is!!!
SomethingElse.LoadFromStream(ZipStream, ZipHeader.UncompressedSize);
ZipStream.Free;
In my opinion, TZipFile should really load the ZipStream with only what is requested. The way this is implemented is not intuitive without first going through the TZipFile source code.
TL;DR: The solution to my problem was an external component. Zip Forge (or Abbrevia)
Read on for details.
Nothing I tried except for the roundabout way of saving the file and re-opening it using David's function worked. While that would have worked, it was not optimal as it required me to save the downloaded file to disk first and reopen it for extract and then delete the zip file. My goal was to just open the downloaded stream and extract the files directly to disk. One write to disk and no temporary file.
We even tried two different C# libraries to zip the files and both gave the same results on the streamed data. The Delphi TZipFile component could not handle it.
It turns out we have a license to ZipForge which I had forgotten about since I had not used it in ages it and that handles the download stream from the C# web server and extracts the files successfully.
For reference, I also tried the Abbrevia component version 5.2 and that also successfully extracted the files from the stream.
Hopefully this will help someone else.
All the suggestions by David and Uwe were appreciated.
Cheers!
TJ

How to resolve "Sevenzip: Error result (00000001) Incorrect function" using JCLcompression unit

I'm using JCL version 2.4.1.4571 with Delphi XE3 and have had no luck decompressing archives. I've downloaded the dll's from JEDI's site and also tried using the 7z.dll (32bit) from 7-zip.org but either way I get the same error when I try to "ExtractAll"
See function below:
function TForm1.Decompress(FileName, DestDir: String): Boolean;
var
archiveclass: TJclDecompressArchiveClass;
Myarchive: TJclDecompressArchive;
begin
Decompress := False;
// Filename = name.7z or name.zip (simple test zips nothing fancy)
// DestDir = fully qualified path to an existing directory
archiveclass := GetArchiveFormats.FindDecompressFormat(FileName);
Try
if Assigned(archiveclass) then
Begin
Myarchive := archiveclass.Create(FileName);
if (Myarchive is TJclSevenZipDecompressArchive) then
Begin
try
Myarchive.ListFiles; { Fails without doing this first }
{ ExtractAll (AutocreateSubDir) must be set true if arc has directories or it will crash }
Myarchive.ExtractAll(DestDir, True);
Decompress := True;
except on E: EJclCompressionError do
Begin
ShowMessage(e.Message);
End;
end;
End
Else
ShowMessage('Not supported by 7z.dll');
End;
Finally
MyArchive.Free;
End;
end;
When I execute the MyArchive.ExtractAll line, I get an exception Sevenzip: Error result (00000001) Incorrect function. I based my code on code from others here on StackOverflow. Am I missing something I need to do first or is this a bug? I've replaced the extract line with MyArchive.ListFiles and get the same error (I saw that in an example here; however, I've yet to divine the purpose of ListFiles.
Compiling to 32bit target.
Edit: Created a series of different types of archives using 7-zip and tried to decompress each with my program. The first thing I discovered is that if the archive contains directories of files, ExtractAll will crash if you don't set the second parameter to True. I then tested archives with different compression methods.
.7z archive using LZMA2 Ultra compression gives the Hresult = 1 error
.zip archive using LZMA Ultra compression gives the Hresult = 1 error
.zip archives using flavors of Deflate or deflate64 all work fine.
It appears that the library doesn't handle LZMA compression at all. Since it makes no sense that the 7z.dll can't handle it, I'm guessing the problem is with the JEDI JCL code. I need to be able to compress/decompress .7z and .zip's using LZMA with this library or I could have just used the built in zip stuff to begin with. Any further suggestions would be appreciated.
I think that is a JCL implementation bug. 7z use COM interfaces, and returns HRESULT codes. JCL attemptes to translate them into error messages using SysErrorMessase(), but AFAIK it works only for Win32 error codes, not HRESULT. That way the return code S_FALSE gets mapped to ERROR_INVALID_FUNCTION (see http://issuetracker.delphi-jedi.org/view.php?id=6348).
My guess is that a 7z call is returning S_FALSE for some reason, because it encounters some issue when decompressing - and the error is deceiving.
See also Error Handling in COM.
Further Googling on this problem turned up http://sourceforge.net/p/jcl/mailman/message/21371812/
It appears "FindDecompressFormat doesn't find archive format if file name is not in lower case."
I tried changing the string I was passing to lowercase and I successfully decompressed an LZMA archive.
archiveclass := GetArchiveFormats.FindDecompressFormat(lowercase(FileName));
JEDI JCL would be a cool library if it had any documentation whatsoever - sad.
If you call TJclZipDecompressArchive with a filename that does not exist you will get the same not very helpful error message on the ListFiles function.
Moral of the story check if the file exists yourself before calling the api.

Copy file to clipboard in Delphi

Anybody know how to copy file in Delphi? It likes press Ctrl+ C on a file or folder, and then we can Paste at somewhere ? I just know how to copy a text by Clipbrd Unit, but i don't know with a file, folder !
Please help me !
Use the CF_HDROP format to store the file/folder's full path, or use the CF_SHELLIDLIST format to store the file/folder's ITEMIDLIST. Refer to MSDN for more details:
Shell Clipboard Formats
Alternatively, convert the folder/file path(s) to ITEMIDLIST value(s) using SHParseDisplayName(), ILCreateFromPath(), or other similar function, then use SHCreateDataObject() to create an IDataObject from them, and then pass that to OleSetClipboard().
Clipboard and drag&drop have a lot in common. Most of libraries for support of drag&drop also have all what you need for clipboard. For example check this lib:
http://melander.dk/delphi/dragdrop/
It is free, with full sourcecode and has a lot of examples, including for clipboard operations.
One of examples is DragDrop\Demos\Unicode\DragDropUnicode.dproj, it has popup command "Copy to clipboard":
procedure TForm1.ActionFileCopyExecute(Sender: TObject);
begin
if (FFiles = '') then
DropFileSource1.Files.Text := LoadResString(0)
else
DropFileSource1.Files.Text := FFiles;
DropFileSource1.CopyToClipboard;
end;
Hope it helps.

Delphi Determine filesize in real time

Is it possible in Delphi to determine the size of a file as it is being copied? I get a notification when a file is first copied to a folder, but need to wait until the copy is complete before I can process the file.
I've used JclFileUtils.GetSizeOfFile(Filename) but that gives me the 'expected' file size, not the current filesize.
Regards, Pieter
Prompted by the first answer I decided to give up on trying to determine when a file copy has completed. Instead I found that using TFileStream gave me a reliable indication whether a file is in use or not.
function IsFileInUse(Filename: string; var ResultMessage: string): boolean;
var
Stream: TFileStream;
begin
Result := True;
ResultMessage := '';
try
Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
try
Result := False;
finally
FreeAndNil(Stream);
end;
Except on E: Exception do
ResultMessage := 'IsFileInUse: ' + E.Message
end;
end;
In this way I can keep on checking until the file is not in use anymore before attempting to process it.
It depends on the technique that is used by the copying function. Most copy-methods will allocate the disk space first before they start to copy a file. Thus, if you want to copy a file of 4 GB, the system starts by creating a file with random data for 4 GB in total. (Which is done lightning-fast, btw.) It then copies the data itself, but the file size is already what you expect.
This has as advantage that the sysmen can check if there's enough disk space available to actually copy the data.
If you write your own file copy function then you can have total control over how it does this. Else, you're limited to whatever the chosen copy-method offers you. So, how do you copy a file?
If you have control over the file copy process, it is easiest to have the copy routine create the file using a temporary filename, and when done, rename it to correct filename.
That way, you can use Windows folder monitoring to watch for the renaming (JCL contains a component to help with this, not sure about the name from here). When your code gets triggered you are sure the other side has finished writing the file.
A simple trick I used was to have the copying process create new files with a '$$$' extension. My code still got triggered for those but I ignored them until they were renamed to their proper filename.
Hope this helps.

Open an ANSI file and Save a a Unicode file using Delphi

For some reason, lately the *.UDL files on many of my client systems are no longer compatible as they were once saved as ANSI files, which is no longer compatible with the expected UNICODE file format. The end result is an error dialog which states "the file is not a valid compound file".
What is the easiest way to programatically open these files and save as a unicode file? I know I can do this by opening each one in notepad and then saving as the same file but with the "unicode" selected in the encoding section of the save as dialog, but I need to do this in the program to cut down on support calls.
This problem is very easy to duplicate, just create a *.txt file in a directory, rename it to *.UDL, then edit it using the microsoft editor. Then open it in notepad and save as the file as an ANSI encoded file. Try to open the udl from the udl editor and it will tell you its corrupt. then save it (using notepad) as a Unicode encoded file and it will open again properly.
Ok, using delphi 2009, I was able to come up with the following code which appears to work, but is it the proper way of doing this conversion?
var
sl : TStrings;
FileName : string;
begin
FileName := fServerDir+'configuration\hdconfig4.udl';
sl := TStringList.Create;
try
sl.LoadFromFile(FileName, TEncoding.Default);
sl.SaveToFile(FileName, TEncoding.Unicode);
finally
sl.Free;
end;
end;
This is very simple to do with my TGpTextFile unit. I'll put together a short sample and post it here.
It should also be very simple with the new Delphi 2009 - are you maybe using it?
EDIT: This his how you can do it using my stuff in pre-2009 Delphis.
var
strAnsi : TGpTextFile;
strUnicode: TGpTextFile;
begin
strAnsi := TGpTextFile.Create('c:\0\test.udl');
try
strAnsi.Reset; // you can also specify non-default 8-bit codepage here
strUnicode := TGpTextFile.Create('c:\0\test-out.udl');
try
strUnicode.Rewrite([cfUnicode]);
while not strAnsi.Eof do
strUnicode.Writeln(strAnsi.Readln);
finally FreeAndNil(strUnicode); end;
finally FreeAndNil(strAnsi); end;
end;
License: The code fragment above belongs to public domain. Use it anyway you like.

Resources