Reputation: 21124
I have a list of records. Each record has an
URL:= string
field. Via GUI the user can edit revords or even delete records (rows) entirely. I would like to download in background in a thread all online files pointed by the URL field. Of course, I don't want to lock the GUI when the thread downloads the files. So, how do I make sure the program/user cannot access the record processed currently by the thread?
Upvotes: 1
Views: 859
Reputation: 1328
Following is based on the solution of Tom Brunberg using a record. Idea that the record will begin downloading via a TThread
(the implementation of the download itself is out of the question as I understand). It might be a bit rough, let me know if there are severe mistakes for example in handling the thread.
While downloading, the data is not accessible, I decided to throw an exception when accessed, but that's up to implementation details of the GUI. property IsDownLoading: Boolean
can be used to e.g. disable controls that would normally make the data accessible, too.
Still, the URL can be changed at all times by user, terminating the current download if in process.
A TDownloadThread
should only be present while needed. If there are lots of these records, this should reduce unneeded resources.
unit Unit1;
interface
uses
System.Classes, System.SysUtils;
type
TDownLoadThread = class(TThread)
private
FURL: string;
FData: Variant;
procedure SetURL(const Value: string);
protected
procedure Execute; override;
public
property Data: Variant read FData;
property URL: string read FURL write SetURL;
end;
TDownLoadRecord = record
private
FData: Variant;
FURL: string;
FDownLoadThread: TDownLoadThread;
procedure DownLoadThreadTerminate(Sender: TObject);
function GetIsDownLoading: Boolean;
procedure SetURL(const Value: string);
procedure URLChanged;
function GetData: Variant;
public
property Data: Variant read GetData;
property URL: string read FURL write SetURL;
property IsDownLoading: Boolean read GetIsDownLoading;
end;
implementation
{ TDownLoadRecord }
procedure TDownLoadRecord.DownLoadThreadTerminate(Sender: TObject);
begin
FData := FDownLoadThread.Data;
FDownLoadThread := nil;
end;
function TDownLoadRecord.GetData: Variant;
begin
if not IsDownLoading then
Result := FData
else
raise Exception.Create('Still downloading');
end;
function TDownLoadRecord.GetIsDownLoading: Boolean;
begin
Result := (FDownLoadThread <> nil) and not FDownLoadThread.Finished;
end;
procedure TDownLoadRecord.SetURL(const Value: string);
begin
if FURL <> Value then
begin
FURL := Value;
URLChanged;
end;
end;
procedure TDownLoadRecord.URLChanged;
begin
if FURL <> '' then
begin
if FDownLoadThread <> nil then
TDownLoadThread.Create(True)
else
if not FDownLoadThread.CheckTerminated then
FDownLoadThread.Terminate;
FDownLoadThread.URL := FURL;
FDownLoadThread.FreeOnTerminate := True;
FDownLoadThread.OnTerminate := DownLoadThreadTerminate;
FDownLoadThread.Start;
end;
end;
{ TDownLoadThread }
procedure TDownLoadThread.Execute;
begin
// Download
end;
procedure TDownLoadThread.SetURL(const Value: string);
begin
FURL := Value;
end;
end.
Upvotes: 2
Reputation: 21033
So, how do I make sure the program/user cannot access the record processed currently by the thread?
In "modern" (sine Delphi 2006 I think) records you can use properties with getters and setters just as with classes. In the setter you can prevent or allow changes to the underlying field.
A naive example:
type
TMyRecord = record
private
FURL: string;
FDownloading: boolean;
procedure SetTheURL(NewURL: string);
public
property TheURL: string read FURL write SetTheURL;
procedure DownLoad;
end;
procedure TMyRecord.SetTheURL(NewURL: string);
begin
if not FDownloading then
FURL := NewURL;
else
// signal inability to change
end;
procedure TMyRecord.DownLoad;
begin
FDownLoading := True;
// hand the downloading task to a thread
end;
Here's the documentation under Records(advanced)
Upvotes: 3
Reputation: 4357
I really like to use BITS for downloads. Access from Delphi is easy. In BITS your define jobs, which are downloaded in background. When ready you can call a EXE, you can poll in the idle loop for the result or you can get an event.
Here is a samples - you will need the jedi lib! That sample needs to be extended for production quality (error handling, logging, job name)!
unit uc_DownloadBits;
interface
uses
ExtActns;
type
TDownloadBits = class
public
class procedure DownloadForground(ziel, downloadurl: WideString; DownloadFeedback:TDownloadProgressEvent);
class procedure DownloadBackground(ziel, downloadurl, ExeName, Params: WideString);
class procedure CompleteJob(JobId: WideString);
end;
implementation
uses
ComObj, ActiveX, SysUtils,
JwaBits, JwaBits1_5, Windows;
{ TDownloadBits }
class procedure TDownloadBits.CompleteJob(JobId: WideString);
var
bi: IBackgroundCopyManager;
job: IBackgroundCopyJob;
g: TGuid;
begin
bi:=CreateComObject(CLSID_BackgroundCopyManager) as IBackgroundCopyManager;
g:=StringToGUID(jobid);
bi.GetJob(g,job);
job.Complete();
end;
class procedure TDownloadBits.DownloadBackground(ziel, downloadurl,
ExeName, Params: WideString);
var
bi: IBackgroundCopyManager;
job: IBackgroundCopyJob;
job2: IBackgroundCopyJob2;
jobId: TGUID;
r: HRESULT;
begin
bi:=CreateComObject(CLSID_BackgroundCopyManager) as IBackgroundCopyManager;
r:=bi.CreateJob('Updatedownload', BG_JOB_TYPE_DOWNLOAD, JobId, job);
if not Succeeded(r) then
raise Exception.Create('Create Job Failed');
r:=Job.AddFile(PWideChar(downloadurl), PWideChar(ziel));
if not Succeeded(r) then
raise Exception.Create('Add File Failed');
// Download starten
Job.Resume();
Params:=Params+' '+GUIDToString(jobId);
Job2 := Job as IBackgroundCopyJob2;
Job2.SetNotifyCmdLine(pWideChar(ExeName), PWideChar(Params));
Job.SetNotifyFlags(BG_NOTIFY_JOB_TRANSFERRED);
end;
class procedure TDownloadBits.DownloadForground(ziel, downloadurl: widestring; DownloadFeedback:TDownloadProgressEvent);
var
bi: IBackgroundCopyManager;
job: IBackgroundCopyJob;
jobId: TGUID;
r: HRESULT;
// Status Zeug
p: BG_JOB_PROGRESS;
s: BG_JOB_STATE;
// Timer Zeug
hTimer: THandle;
DueTime: TLargeInteger;
c: boolean;
begin
bi:=CreateComObject(CLSID_BackgroundCopyManager) as IBackgroundCopyManager;
r:=bi.CreateJob('Updatedownload', BG_JOB_TYPE_DOWNLOAD, JobId, job);
if not Succeeded(r) then
raise Exception.Create('Create Job Failed');
r:=Job.AddFile(PWideChar(downloadurl), PWideChar(ziel));
if not Succeeded(r) then
raise Exception.Create('Add File Failed');
// Download starten
Job.Resume();
DueTime:=-10000000;
hTimer:=CreateWaitableTimer(nil, false, 'EinTimer');
SetWaitableTimer(hTimer, DueTime, 1000, nil, nil, false);
while True do
begin
Job.GetState(s);
if s in [BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_TRANSFERRED] then
begin
Job.GetProgress(p);
DownloadFeedback(nil, p.BytesTransferred, p.BytesTotal, dsDownloadingData, '', c);
if c then
break;
end;
if s in [BG_JOB_STATE_TRANSFERRED,
BG_JOB_STATE_ERROR,
BG_JOB_STATE_TRANSIENT_ERROR] then
break;
WaitForSingleObject(hTimer, INFINITE);
end;
CancelWaitableTimer(hTimer);
CloseHandle(hTimer);
if s=BG_JOB_STATE_TRANSFERRED then
job.Complete();
job:=nil;
bi:=nil;
end;
end.
Upvotes: 3