Moving the content of a folder to another one - file

Using Delphi 7, I need to move all the content (files, folders and subfolders) from one folder to another one. After some research, SHFileOpStruct seems to be the best option. Here is what I got so far:
function MoveDir(SrcDir, DstDir: string): Boolean;
var
FOS: TSHFileOpStruct;
begin
ZeroMemory(#FOS, SizeOf(FOS));
with FOS do
begin
wFunc := FO_MOVE; // FO_COPY;
fFlags := FOF_FILESONLY or
FOF_ALLOWUNDO or FOF_SIMPLEPROGRESS;
pFrom := PChar(SrcDir + #0);
pTo := PChar(DstDir + #0);
end;
Result := (SHFileOperation(FOS) = 0);
end;
But when using this function, the entire folder is moved to destination, not only it content. For example, if I use MoveDir('c:\test', 'd:\test') I get d:\teste\teste.
I already tried to change this line bellow, and it works when coping the files (FO_COPY) but not when moving.
pFrom := PChar(SrcDir + '\*.*' + #0);
Please, can someone help me with this? Would be great if I can do this without moving file by file, folder by folder...
Thanks!!

You should use your second version, without FOF_FILESONLY flag:
function MoveDir(SrcDir, DstDir: string): Boolean;
var
FOS: TSHFileOpStruct;
begin
ZeroMemory(#FOS, SizeOf(FOS));
with FOS do
begin
wFunc := FO_MOVE; // FO_COPY;
fFlags := FOF_ALLOWUNDO or FOF_SIMPLEPROGRESS;
pFrom := PChar(IncludeTrailingPathDelimiter(SrcDir) + '*.*'#0);
pTo := PChar(DstDir + #0);
end;
Result := (SHFileOperation(FOS) = 0);
end;

Related

Delphi Text File and Array

I have the following Scenario: In a textfile (justin#us883) the program must extract the password - us883 check if the password typed in is correct to the one in the list and then make a button visible to enter program: Code that is not working:
var
textf:textfile;
oneline,spass,scheck :string;
place,i,icount :integer;
Arrpass : array[1..Maxnames] of string;
begin
scheck := edtpass.Text;
assignfile(textf,'Userlist.txt');
reset(textf);
if fileExists('Userlist.txt')= false then
exit;
while not eof(textf) do
begin
Readln(textf, oneline);
place := pos('#',oneline);
delete(oneline,1,place);
spass := copy(oneline,1,place-1); // get the us883
Arrpass[i] := spass;
for i := Low(Arrpass) to High(Arrpass) do
if Arrpass[i] = spass then
begin
btnenter.Visible := true
end
else
btnenter.Visible := False;
Showmessage('Wrong Password');
end;
closefile(textf);
end;
Maybe you want to do it more delphi like:
var
list : TStringList;
user, password : String;
i : Integer;
begin
user := 'user1';
if fileExists('Userlist.txt')= false then exit;
list := TStringList.create();
list.NameValueSeparator := '#';
list.LoadFromFile('Userlist.txt');
i := list.IndexOfName(user);
if i >=0 then begin
password := list.Values[user];
// check password
end;
Issue 1:
place := pos('#',oneline);
delete(oneline,1,place);
spass := copy(oneline,1,place-1); // get the us883
The third line uses position of # that is not more actual (after deletion).
If you don't expect more symbols after password, just use
spass := oneline;
Issue 2:
How Arrpass array sould be filled?
This line Arrpass[i] := spass; uses uninitialized variable i. And what is a logic to compare just inserted value with itself? Perhaps you want to fill ArrPass before text reading.
Issue 3:
It is worth to check if fileExists before file handling.

Select items from a combobox and write index of selected value to an INI file during installation

How to make the user select an item from a combobox and then write it to an INI file as a number (01, 02, 03, ..., 18)? I generated this code but I do not know what else to do. If you could help me I would be very grateful
[INI]
Filename: "{app}\rev.ini"; Section: "steamclient"; Key: "RankLevel"; String: ""
[Code]
var
NewComboBox1: TNewComboBox;
procedure RedesignWizardForm;
begin
{ NewComboBox1 }
NewComboBox1 := TNewComboBox.Create(WizardForm);
with NewComboBox1 do
begin
Name := 'NewComboBox1';
Parent := WizardForm.SelectDirPage;
Left := ScaleX(0);
Top := ScaleY(120);
Width := ScaleX(145);
Height := ScaleY(21);
Text := 'Alcon';
Items.Text := 'Alcon' + #13#10 +
'Aguila' + #13#10 +
'Elite' + #13#10 +
'Pro';
ItemIndex := 0;
end;
NewComboBox1.TabOrder := 5;
end;
procedure InitializeWizard();
begin
RedesignWizardForm;
end;
The easiest solution is using a scripted constant in the INI section:
[INI]
Filename: "{app}\rev.ini"; Section: "steamclient"; \
Key: "RankLevel"; String: "{code:GetRankLevel}"
[Code]
{ ... }
function GetRankLevel(Param: string): string;
begin
Result := Format('%.2d', [NewComboBox1.ItemIndex + 1]);
end;
Though this will not write the INI file "in a custom page after the components section". It will write it only during the actual installation, what is the correct behavior, imho.
See also Save Inno Setup custom page field values to an INI file.

Inno Setup - Create a dynamic list of components/types from external source (file or folder contents)

I have a batch file (setting changer) that uses xcopy to list specific files formats in a specific folder, then allows me to type in one of the names and the script uses that name to copy that file to another location.
First xcopy creates a copy of the original as a backup (rolling backup only 1 copy) then does the file copy (extension is fixed in batch only body of filename needed This works great BUT I would love to try and do this in Inno Setup for a nice clean GUI.
I would like to populate the list of components/types from this list of files found in a specific fixed folder. Or even create an ini file with those names in (extra step but maybe better control). Main problem that might prevent this from being possible is not knowing how many entries it would be an array. If only 1 entry or file only 1 option (1 or a) if 4 then user can select 1 of 4 (a, b, c or d). I would extract the file name to create the name/description.
then on completion the same task as my batch would happen, backup (easy always the same name like start.ini) then copy the file e.g. example1.ini and overwrite start.ini
ini might be most flexible as I can imagine later adding new sections to change the actions performed.
Is this possible or stretching this program too far. No rush as my batch works for now but the less typing and manual steps the better for continued use.
I found an example to list contents to a dialog window but I could not figure how to use this to populate components. TLama - List all files in a directory
You cannot create the components dynamically on runtime (you can on compile-time).
But it's not difficult to implement a custom dynamic components-like page using the CreateCustomPage and the TNewCheckListBox.
Then in the CurStepChanged(ssInstall) you process the selected files/components as your need.
[Code]
const
SourcePath = 'C:\somepath';
var
CustomSelectTasksPage: TWizardPage;
ComponentsList: TNewCheckListBox;
procedure InitializeWizard();
var
FindRec: TFindRec;
SelectComponentsLabel: TNewStaticText;
begin
CustomSelectTasksPage :=
CreateCustomPage(
wpSelectComponents, SetupMessage(msgWizardSelectComponents),
SetupMessage(msgSelectComponentsDesc));
SelectComponentsLabel := TNewStaticText.Create(WizardForm);
SelectComponentsLabel.Parent := CustomSelectTasksPage.Surface;
SelectComponentsLabel.Top := 0;
SelectComponentsLabel.Left := 0;
SelectComponentsLabel.Width := CustomSelectTasksPage.Surface.Width;
SelectComponentsLabel.AutoSize := False;
SelectComponentsLabel.ShowAccelChar := False;
SelectComponentsLabel.WordWrap := True;
SelectComponentsLabel.Caption := SetupMessage(msgSelectComponentsLabel2);
WizardForm.AdjustLabelHeight(SelectComponentsLabel);
ComponentsList := TNewCheckListBox.Create(WizardForm);
ComponentsList.Parent := CustomSelectTasksPage.Surface;
ComponentsList.Top :=
SelectComponentsLabel.Top + SelectComponentsLabel.Height + ScaleY(8);
ComponentsList.Left := 0;
ComponentsList.Width := CustomSelectTasksPage.Surface.Width;
ComponentsList.Height := CustomSelectTasksPage.Surface.Height - ComponentsList.Top;
if FindFirst(ExpandConstant(AddBackslash(SourcePath) + '*.dat'), FindRec) then
begin
try
repeat
ComponentsList.AddCheckBox(FindRec.Name, '', 0, False, True, False, False, nil);
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
I: Integer;
FileName: string;
SourceFilePath: string;
TargetFilePath: string;
begin
if CurStep = ssInstall then
begin
for I := 0 to ComponentsList.Items.Count - 1 do
begin
if ComponentsList.Checked[I] then
begin
FileName := ComponentsList.Items[I];
SourceFilePath := AddBackslash(SourcePath) + FileName;
TargetFilePath := AddBackslash(ExpandConstant('{app}')) + FileName;
if FileCopy(SourceFilePath, TargetFilePath, False) then
begin
Log(Format('Installed "%s".', [FileName]));
end
else
begin
Log(Format('Failed to install "%s".', [FileName]));
end;
end;
end;
end;
end;

What is the proper way to dynamically create and call a stored procedure in Delphi using FireDac?

I am relatively new to FireDAC. I want to be able to call a stored procedure "on the fly", dynamically. So far I have the following:
function TForm21.ExecuteStoredProc(aSPName: string; aParams: TADParams): Boolean;
var
LSP: TADStoredProc;
i: Integer;
begin
LSP := TADStoredProc.Create(nil);
try
LSP.Connection := ADConnection1;
LSP.StoredProcName := aSPName;
LSP.Prepare;
for i := 0 to aParams.Count - 1 do
begin
LSP.Params[i].Value := aParams[i].Value;
end;
LSP.ExecProc;
finally
LSP.Free;
end;
Result := True;
end;
I call it with
procedure TForm21.Button1Click(Sender: TObject);
var
LParams: TADParams;
begin
LParams := TADParams.Create;
LParams.Add.Value := 612;
LParams.Add.Value := '2008';
ExecuteStoredProc('HDMTEST.dbo.spARCHIVE_HISTORY_DATA', LParams);
end;
However, the stored procedure fails to execute. That is, the code runs fine, no error message is shown, but the stored procedure doesn't run.
Further info -- it runs fine if I drop a component and set up the params in code.
Anyone have any idea what I am missing?
Seeing as this q has been left unanswered for a while, I thought I'd try to get the code working without using the clues from the comments and found it not quite as easy as I'd imagined.
I immediately got stuck with the SP params. I found this
http://docwiki.embarcadero.com/RADStudio/XE5/en/TFDQuery,_TFDStoredProc_and_TFDUpdateSQL_Questions
which says
"If you have difficulties with manual definition of parameters,
populate the Params collection automatically and check how the
parameters are defined. Then compare that to your code. "
but I couldn't find a way of "automatically" populating the Params. I asked on the EMBA
FireDac newsgroup and the FD author, Dimitry Arefiev, kindly explained that
you can do that by checking that the FetchOptions include fiMeta, and then clearing and setting the FDStoredProc's StoredProcName.
Using a StoredProc in the pubs demo database on my SqlServer defined as follows:
create procedure test(#ANumber int, #AName varchar(20))
as
begin
select
#ANumber * 2 as "Number",
#AName + #AName as "Name"
end
I changed a couple of sections of the OP's code like this
[...]
LSP.Params.Clear;
LSP.StoredProcName := '';
LSP.FetchOptions.Items := LSP.FetchOptions.Items + [fiMeta];
LSP.StoredProcName := aSPName;
LSP.Prepare;
Assert(LSP.ParamCount > 0);
for i := 0 to aParams.Count - 1 do
begin
LSP.Params.ParamByName(aParams[i].Name).Value := aParams[i].Value;
end;
[...]
procedure TForm21.Button1Click(Sender: TObject);
var
LParams: TFDParams;
Param : TFDParam;
begin
LParams := TFDParams.Create;
Param := LParams.Add;
Param.Name := '#ANumber';
Param.Value := 612;
Param := LParams.Add;
Param.Name := '#AName';
Param.Value := '2008';
ExecuteStoredProc('test', LParams);
end;
and it worked fine.
The OP mentions in the q he'd first had the problem that the SP failed to execute
but that he'd found that it worked if he "[dropped] a component and set up the params in code" so I thought I'd include here a console application where of course necessarily everything is done in code. This wasn't difficult, but the time it took me to get the Uses clause right is my main reason for posting this as an answer, for future reference. W/o the correct uses, you get errors complaining about various class factories being missing.
Console app (created and tested in XE6):
program ConsoleStoredProcProject3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, FireDac.DApt, FireDAC.Stan.Def, FireDAC.Stan.ASync,
FireDAC.Stan.Param, FireDAC.Stan.Option, FireDAC.Comp.Client,
FireDAC.Phys.MSSQL, VCL.ClipBrd;
procedure TestSP;
var
Connection : TFDConnection;
StoredProc : TFDStoredProc;
Param : TFDParam;
begin
Connection := TFDConnection.Create(Nil);
Connection.DriverName := 'MSSQL';
Connection.Params.Values['Server'] := // your server name'';
Connection.Params.Values['Database'] := 'pubs';
Connection.Params.Values['user_name'] := 'user'; // adjust to suit
Connection.Params.Values['password'] := 'password'; // ditto
Connection.LoginPrompt := False;
Connection.Connected := True;
StoredProc := TFDStoredProc.Create(Nil);
StoredProc.Connection := Connection;
StoredProc.FetchOptions.Items := StoredProc.FetchOptions.Items + [fiMeta];
StoredProc.StoredProcName := 'test';
StoredProc.Prepare;
Param := StoredProc.Params.ParamByName('#ANumber');
Param.Value := 333;
Param := StoredProc.Params.ParamByName('#AName');
Param.Value := 'A';
StoredProc.Active := True;
WriteLn(StoredProc.FieldByName('Number').AsInteger);
WriteLn(StoredProc.FieldByName('Name').AsString);
ReadLn;
end;
begin
try
TestSP;
except
on E: Exception do
Clipboard.AsText := E.Message;
end;
end.

open database with initfile

how to open a database local or remote with IniFile.
something like the below.
vBanco : String;
IniFileName : TIniFile;
begin
IniFileName := TIniFile.Create(ExtractFilePath(ParamStr(0))+FileName);
Try
if FileExists (remote+'db\ado.mdb') then
begin
vBanco := Trim(IniFileName.ReadString('acesso','BancoRemto',''));
Dirlocal := Trim(IniFileName.ReadString('acesso','PastasRemto',''));
frmPrincipal.Edit1.text := Dirlocal;
Dirtrabalho := (ExtractFilePath(Application.ExeName));
Conection.ConnectionString := vBanco;
end
else
begin
Try
vBanco := Trim(IniFileName.ReadString('acesso','banco',''));
Dirlocal := Trim(IniFileName.ReadString('acesso','PastasLocais',''));
frmPrincipal.Edit1.text := Dirlocal;
Dirtrabalho := (ExtractFilePath(Application.ExeName));
Finally
end;
end;
Finally
IniFileName.Free;
end;
end;
It seems as if you have your code correct, if you are running into problems make sure the value of the INI Connection string is valid. A Good way to get a valid connect string is to setup the connection at design time then copy and paste it to your config file.
If your problem is with FileExists, here's how you track it down.
Put a breakpoint on the if FileExists line. When it breaks to the debugger, hit CTRL-F7, which will bring up the Evaluate/Modify dialog. Type remote+'db\ado.mdb' into the input box and see what it gives. It'll most likely give you a bad filename.
For example, if remote doesn't end in a backslash, then that will yield an invalid path. You can fix this with the IncludeTrailingPathDelimiter function. Hard to be certain without seeing more of your code, but from my experience that's probably what's going on.
I found my mistake. Instead of checking the existence of a file named "ado.mdb" in a directory based on the global variable remote, I should have been looking for a file named "base_dados.mdb" in a directory determined by a value I read from an INI file.
procedure TfrmDados.ConectionBeforeConnect(Sender: TObject);
const
FileName = 'config.ini';
var
vBanco : String;
IniFileName : TIniFile;
LBasedados : String;
begin
IniFileName := TIniFile.Create(ExtractFilePath(ParamStr(0))+FileName);
vBanco := Trim(IniFileName.ReadString('acesso','BDtrabalho',''));
Dirlocal := Trim(IniFileName.ReadString('acesso','Pastrabalho',''));
LBasedados := Dirlocal+'db\base_dados.mdb';
frmPrincipal.Edit1.text := Dirlocal;
Dirtrabalho := (ExtractFilePath(Application.ExeName));
if FileExists(LBasedados) then
Conection.ConnectionString := vBanco
else
begin
Application.MessageBox('Bade dados do servidor não encontrada vai trabalhar neste Computador!','Ligação Remota FALHOU');
vBanco := Trim(IniFileName.ReadString('acesso','BD_local',''));
Dirlocal := Trim(IniFileName.ReadString('acesso','Pasta_local',''));
frmPrincipal.Edit1.text := Dirlocal;
Dirtrabalho := (ExtractFilePath(Application.ExeName));
Conection.ConnectionString := vBanco;
end;
IniFileName.Free;
end;

Resources