How To check if zipfile already has been downloaded - delphi

I have made a Delphi application which downloads a zipfile (update.zip) at a regular interval. In the zipfiles there are DLL and Exe.
The zipfile is unzipped and the DLLs and Exes are copied to the correct folder.
What I want to Know is how can I know if the zipfile has been downloaded by the client so it doesn't have to download it again. Because it has already been processed by the client. But when the contents of the zipfile has changed then
it must download the zipfile again.
The contents of the zipfiles can change if we build a new DLL or Exe. But the name of the zipfile is the Same.

If you want to know whether the zip file has changed without downloading the zip file, then your server will have to provide some other way for you to discover what versions of DLLs and EXEs are on the server. That could be as simple as keeping a text file on the server. Download that file instead of the whole zip file. If the versions in that text file are newer than the versions you have locally, then download the zip file.
You can also avoid processing the zip file by deleting it after you've processed it the first time. Instead of comparing versions in the text file with versions of files in the local zip file, you can compare the text-file versions with the versions of the actual files on disk.

you could do a HTTP HEAD; and check the file last-modified date on server and local if changed download the new file.
uses
......, IdHTTP;
function getHTTPLastModified(url: string): TDateTime;
var
HTTP: TIdHTTP;
begin
try
try
HTTP := TIdHTTP.Create(nil);
HTTP.Head( url );
result:=HTTP.Response.LastModified;
except
on E: Exception do
//ShowMessage('ProcessHttpRequest failed.');
result := 0;
end;
finally
try
HTTP.Disconnect;
except
end;
end;
end;

Related

How to share an application and associated datafile

I have written a program (firemonkey) using delphi community edition. I would like to share the program, but the .exe file that my friends will be downloading has to access a text file from time to time to retrieve strings. When writing the program I used an event handler, to load the text file at startup:
tform3.formCreate (Sender:Tobject);
...
assignfile(myfile,('C:**********.txt));
...
Worked just fine during the
design stage.
As a hobbyist, I now find myself stuck. If I use INNO setup compiler to create an installation program, which I plan to do, I can't have this same hardwired reference ('C:*****) to the data file's location. What I need is to change the above code such that the .exe file can locate the supporting datafile irrespective of where that .exe file (and datafile) ends up on someone else's PC.
How can I do this? i.e. What code do I need (in place of the above) to ensure that the installation program I hand out will install an .exe file that can locate the data file it references?
Any help, much appreciated. Still learning.
Read-only access
If the data file should always be opened in read-only mode, the simplest solution is to place it next to the *.exe file. Then, at runtime, you dynamically find the path to the *.exe file and modify it to find the path to the data file. For instance,
uses
IOUtils;
procedure TForm1.FormCreate(Sender: TObject);
var
FileName: string;
begin
FileName := TPath.Combine(ExtractFilePath(ParamStr(0)), 'data.txt');
ShowMessage(TFile.ReadAllText(FileName, TEncoding.UTF8));
end;
ParamStr(0) contains the path to the *.exe file, such as
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\Project1.exe'
Then ExtractFilePath(ParamStr(0)) is
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\'
and, finally, TPath.Combine(ExtractFilePath(ParamStr(0)), 'data.txt') is
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\data.txt'
Make sure the installer puts the data file next to the *.exe file.
Read and write access
If we are talking about a settings file or some other file that each user needs to change (via the software), you cannot place it next to the *.exe file, because the *.exe file typically resides in the Program Files folder, which is read only. Also, there is only one Program Files folder, but possibly many users on the PC, and each user should have his or her own copy.
The solution is to save the file in the user's own folders, specifically, the AppData folder:
FileName := TPath.GetHomePath + '\Mariner\My Word Processor App\Settings\settings.ini';
(using a different approach to path building).
On my system, this becomes
'C:\Users\Andreas Rejbrand\AppData\Roaming\Mariner\My Word Processor App\Settings\settings.ini'
Your installer (Inno Setup) has built-in support for placing files in this location.
If it is only accessed read only, you could also consider adding it as a resource to the executable. Which would then allow you to simply distribute this executable without the need for an installer.
Delphi Dabbler has an example, but I found it a bit confusing. I'll link to it (PDF) anyway.
You can let the user select the place where the file has to be saved. Proposing AppData folder if the file is for each individual user or CommonAppData if the file has to be shared between different users.
When the use selected the data file destination, you can save it to an INI file. The INI file can be stored, without asking the user, either to the registry or to an INI file saved in the AppData folder or ProgramData folder.
Here is a snipped of source code to get hand on those special folders:
const
SectionWindow = 'Window';
SectionData = 'Data';
CompanyFolder = 'YourCompanyName';
constructor TForm1.Create(AOwner: TComponent);
var
CommonPath : array [0..MAX_PATH] of Char;
LocalPath : array [0..MAX_PATH] of Char;
LangFileName : String;
begin
SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, SHGFP_TYPE_CURRENT, #CommonPath[0]);
SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, SHGFP_TYPE_CURRENT, #LocalPath[0]);
FIniSection := SectionWindow;
FIniSectionData := SectionData;
FAppName := ChangeFileExt(ExtractFileName(Application.ExeName), '');
FCommonAppData := IncludeTrailingPathDelimiter(CommonPath) +
CompanyFolder + '\' + FAppName + '\';
FLocalAppData := IncludeTrailingPathDelimiter(LocalPath) +
CompanyFolder + '\' + FAppName + '\';
FIniFileName := FLocalAppData + FAppName + '.ini';
ForceDirectories(FCommonAppData);
ForceDirectories(FLocalAppData);
inherited Create(AOwner);
end;

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

Extract zip file to TStream using zlibar in Lazarus

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);.

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.

Delphi 7 and Excel 2007 Open File Error

I am having difficulty opening a EXCEL 2007 in Delphi 7 It works for Office 2003 and below but the wonderful people at microsoft have sent an update or something and the delphi app fell over just earlier this month.
oE := GetActiveOleObject('Excel.Application');
oE.Workbooks.Open(Filename:=sFilename, UpdateLinks:=false, ReadOnly:=true); //Error
I get the following error:
'c:\Temp\Book1.xls' could not be
found. Check the spelling of the file
name, and verify that the file
location is correct.'#$A#$A'If you are
trying to open the file from your list
of most recently used files, make sure
that the file has not been renamed,
moved, or deleted'
Yet if I run the same command in VBA there is no problem.
I know this sounds stupid, but have you manually confirmed that the file exists at that location?
What exactly is the contents of sFileName, is it the full path or only the filename? When it is only the filename, maybe Excel can't find it because its current working directory is something else. If you are only passing the filename, try the full path instead.
the full code for the lookers :
uses ComObj; ..
procdure startExcel;
var
oE:Variant;
begin
try
oE := GetActiveOleObject('Excel.Application');
except
oE := CreateOleObject('Excel.Application');
end;
oE.Workbooks.Open(filename, false, false);
oE.Visible := True;
end;
source

Resources