I use FindFirstFile() and FindNextFile() to list files of a directory. When I call FindFirstFile(), I have to give a search path to it. It returns a handle that can be used by FindNextFile(). Is there a WinAPI function that can get the previously given path by the handle?
Just store that information like you store the search handle already: in a variable. Then create your own wrapper functions for both FindFirstFileA() and FindNextFileA():
type
// What you want to give back per file system object
TMyFindInfo= record // Whatever you want to do here on your own
wfd: Windows.WIN32_FIND_DATAA; // Just provide this as-is because it already everything
end;
// Not only storing the handle, but also other details
TMyFindHandle= record
h: THandle; // Search resource
sFilter: String; // Original query
iMatches, // How often did the search yield a file system object?
iError: Cardinal; // Which error has occured? 0=ERROR_SUCCESS.
end;
function MyFindFile1st
( const sFilter: String
; out vInfo: TMyFindInfo
): TMyFindHandle;
begin
result.sFilter:= sFilter;
result.h:= Windows.FindFirstFileA( PChar(sFilter), vInfo.wfd );
if result.h= INVALID_HANDLE_VALUE then begin
result.iError:= Windows.GetLastError();
case result.iError of
ERROR_FILE_NOT_FOUND: ; // The only error we don't need to display
else // Most likely ERROR_PATH_NOT_FOUND
Windows.MessageBoxA
( Form1.Handle
, PChar('Error initializing search "'+ result.sFilter
+ '": 0x'+ IntToHex( result.iError, 8 )) // Get text message elsewhere
, PChar('Error')
, MB_ICONSTOP
);
end;
result.iMatches:= 0;
ZeroMemory( #vInfo, SizeOf( vInfo ) ); // Nothing to see here
end else begin
result.iError:= ERROR_SUCCESS;
result.iMatches:= 1;
end;
end;
function MyFindFile2nd
( var vHandle: TMyFindHandle
; out vInfo: TMyFindInfo
): Boolean;
begin
result:= Windows.FindNextFileA( vHandle.h, vInfo.wfd );
if not result then begin
vHandle.iError:= Windows.GetLastError();
case vHandle.iError of
ERROR_SUCCESS, // The only errors we don't need to display
ERROR_NO_MORE_FILES: ;
else
Windows.MessageBoxA
( Form1.Handle
, PChar('Error during search "'+ vHandle.sFilter // Original filter from 1st call
+ '" after '+ IntToStr( vHandle.iMatches )+ ' elements occured: 0x'
+ IntToHex( vHandle.iError, 8 ))
, PChar('Error')
, MB_ICONSTOP
);
end;
Windows.ZeroMemory( #vInfo, SizeOf( vInfo ) ); // Nothing to see here
if not Windows.FindClose( vHandle.h ) then begin // Release resource
vHandle.iError:= Windows.GetLastError();
case vHandle.iError of
ERROR_SUCCESS: ;
else // Yes, this can fail, too
Windows.MessageBoxA
( Form1.Handle
, PChar('Error finalizing search "'+ vHandle.sFilter // Original filter from 1st call
+ '" after '+ IntToStr( vHandle.iMatches )+ ' elements occured: 0x'
+ IntToHex( vHandle.iError, 8 ))
, PChar('Error')
, MB_ICONSTOP
);
end;
end;
end else Inc( vHandle.iMatches ); // One more match
end;
// Now the example on how to use it
procedure TForm1.Button1Click(Sender: TObject);
var
vHandle: TMyFindHandle;
vInfo: TMyFindInfo;
begin
vHandle:= MyFindFile1st( 'C:\Windows\*.exe', vInfo );
while vHandle.iError= ERROR_SUCCESS do begin
Memo1.Lines.Add( vInfo.wfd.cFileName );
MyFindFile2nd( vHandle, vInfo ); // Don't even need the Boolean result here
end;
Memo1.Lines.Add( '= '+ IntToStr( vHandle.iMatches )+ ' FS objects' ); // Not only files
end;
At no time there is a need to re-request a detail by handle, because you can keep that detail right with the handle that you need to take care of anyway. Just put both together into a record and pass that to your own functions.
My code is for demonstration purposes (although I think it's a rather trivial overall case). I discourage from displaying dialog windows right in those functions, but instead react upon what vHandle.iError contains where I called those functions.
Related
I'm using CEF4Delphi to browse a site, this site asks for a certificate installed on windows, the documentation says I need to select this certificate in the "SelectClientCertificate" event in a callback function passing the index, my question is how to show this certificate window to select one of them
procedure TFPrin.WebBCSelectClientCertificate(Sender: TObject;
const browser: ICefBrowser; isProxy: Boolean; const host: ustring;
port: Integer; certificatesCount: NativeUInt;
const certificates: TCefX509CertificateArray;
const callback: ICefSelectClientCertificateCallback; var aResult: Boolean);
begin
aresult:=true;
//show certificate window here?
callback.Select(certificates[Certindex]);
end;
would it be the same window when accessing by firefox or chrome?
I appreciate any help, thanks!
I don't understand where the big deal is: you just display either a Form that you've designed just like any other Form, or you temporarily create one on the fly, just to destroy it again. How you plan to display each certificate (level of detail, fancyness, colors...) is up to you and (of course) works better with a Form already designed.
This is an example with a Form created on the fly:
procedure TFPrin.Chromium1SelectClientCertificate
( Sender: TObject
; const browser: ICefBrowser
; isProxy: Boolean
; const host: uCEFTypes.ustring
; port: Integer
; certificatesCount: Cardinal
; const certificates: TCefX509CertificateArray
; const callback: ICefSelectClientCertificateCallback
; var aResult: Boolean
);
var
iCert: Integer; // Which certificate we're just analyzing
sLine: String; // Information about the current certificate
frm: TForm; // Displayed (temporary) modal window
lbx: TListBox; // All certificates to choose from
pan: TPanel; // For the buttons
// Converting a certificate time
function _TimeToStr( vTime: TCefTime ): String;
begin
result:= IntToStr( vTime.year )+ '-'
+ IntToStr( vTime.month )+ '-'
+ IntToStr( vTime.day_of_month );
end;
begin
// Create temporary form...
frm:= TForm.Create( Application );
with frm do begin
try
BorderStyle:= bsSizeable;
// ...along with its temporary controls:
// Bottom panel, which will contain both buttons
pan:= TPanel.Create( frm );
with pan do begin
Parent:= frm;
Align:= alBottom;
Height:= 30;
end;
// Buttons that automatically set the form's modal result
with TButton.Create( frm ) do begin
Parent:= pan;
Caption:= '&Ok';
ModalResult:= ID_OK;
Default:= True; // We can press ENTER anywhere to trigger this button
Top:= 3;
Left:= 10;
end;
with TButton.Create( frm ) do begin
Parent:= pan;
Caption:= '&Cancel';
ModalResult:= ID_CANCEL;
Cancel:= True; // We can press ESC anywhere to trigger this button
Top:= 3;
Left:= 100;
end;
// A list displaying one certificate per line to choose from
lbx:= TListBox.Create( frm );
with lbx do begin
Parent:= frm;
Align:= alClient;
end;
// Now going thru all certificate details and adding each resulting text line to the listbox
for iCert:= Low( certificates ) to High( certificates ) do begin
sLine:= 'Subject: '+ certificates[iCert].GetSubject().GetDisplayName()+ '. '
+ 'Issuer: '+ certificates[iCert].GetIssuer().GetDisplayName()+ '. '
+ 'Valid from '+ _TimeToStr( certificates[iCert].GetValidStart() )+ ' to '
+ _TimeToStr( certificates[iCert].GetValidExpiry() )+ '.';
lbx.Items.Add( sLine );
end;
if lbx.Count> 0 then lbx.ItemIndex:= 0; // Pre-select first certificate
// Display the form and check if the "Ok" button has been pressed and a line is selected.
// If yes, actually choose a certificate.
aResult:= (ShowModal()= ID_OK) and (lbx.ItemIndex<> -1);
if aResult then callback.Select( certificates[lbx.ItemIndex] );
finally
// Free temporary form and all its controls
frm.Free;
end;
end;
end;
And this is an example for calling one of your existing Forms:
uses
frmOther;
procedure TFPrin.Chromium1SelectClientCertificate
...
var
iCert: Integer; // Which certificate we're just analyzing
sLine: String; // Information about the current certificate
// Converting a certificate time
function _TimeToStr( vTime: TCefTime ): String;
begin
result:= IntToStr( vTime.year )+ '-'
+ IntToStr( vTime.month )+ '-'
+ IntToStr( vTime.day_of_month );
end;
begin
// Remove any existing entries in TFOther
FOther.lbxCert.Clear();
// Now going thru all certificate details and adding each resulting text line to the listbox
for iCert:= Low( certificates ) to High( certificates ) do begin
sLine:= 'Subject: '+ certificates[iCert].GetSubject().GetDisplayName()+ '. '
+ 'Issuer: '+ certificates[iCert].GetIssuer().GetDisplayName()+ '. '
+ 'Valid from '+ _TimeToStr( certificates[iCert].GetValidStart() )+ ' to '
+ _TimeToStr( certificates[iCert].GetValidExpiry() )+ '.';
FOther.lbxCert.Items.Add( sLine );
end;
if FOther.lbxCert.Count> 0 then FOther.lbxCert.ItemIndex:= 0; // Pre-select first certificate
// Display the form and check if the "Ok" button has been pressed and a line is selected.
// If yes, actually choose a certificate.
aResult:= (FOther.ShowModal()= ID_OK) and (FOther.lbxCert.ItemIndex<> -1);
if aResult then callback.Select( certificates[FOther.lbxCert.ItemIndex] );
end;
Using the types/interfaces can't be more straight - just look at their definitions:
TCefX509CertificateArray is defined in uCEFInterfaces.pas,
along with everything that also comes with that: ICefX509Certificate and ICefX509CertPrincipal.
TCefTime is defined in uCEFTypes.pas.
What is wrong in this code ? I don't understend, if I remove the "Try" my app dont open, and if don't remove always appear "need login" ...
procedure TF_login.FormActivate(Sender: TObject);
var
Result: Integer;
TextFile: TStringList;
VarArquivo: string;
text: string;
dataI, dataF : string;
begin
TextFile := TStringList.Create;
VarArquivo := System.IOUtils.TPath.GetDocumentsPath + PathDelim + 'Limit.txt';
try
TextFile.LoadFromFile(VarArquivo);
text := TextFile.Text;
// ShowMessage(TextFile.Text); // there is the text
// ShowMessage(text); // there is the text
dataI := FormatDateTime('dd/mm/yyyy', Now);
dataF := FormatDateTime('dd/mm/yyyy', StrToDate(text));
Result := CompareDate(StrToDate(dataI), StrToDate(dataF));
ShowMessage(dataF +' data f');
ShowMessage(dataI +' data I');
if ( Result = LessThanValue ) then
begin
ShowMessage('data F low');
end
else
begin
ShowMessage('data F high');
F_inicio.Show;
end;
FreeAndNil(TextFile);
except on E:
Exception do ShowMessage('An error happened!' + sLineBreak + '[' +
E.ClassName + '] ' + E.Message);
end;
end;
The error : [EConvertError] '09/11/2019' is not a valid date
to create the file, i do:
procedure TF_login.btn_entrarClick(Sender: TObject);
var
data : tdatetime;
Resposta, data_s: string;
begin
PathFile := System.IOUtils.TPath.GetDocumentsPath;
NameFile := 'Limit.txt';
data := Now; //data actual
data := IncMonth(data, 2);
data_s := FormatDateTime('dd/mm/yyyy', data);
TFile.WriteAllText(TPath.Combine(PathFile, NameFile), data_s );
F_inicio.Show;
end;
The file exists, because the first (and second) ShowMessage (what is commented) show me the "09/11/19" but the third and fourth not appear to me...
OBS: Delphi 10.3 (RIO), Plataform: Android
There are a couple of things that you should change in your code:
procedure TF_login.FormActivate(Sender: TObject);
var
TextFile: TStringList;
VarArquivo: string;
text: string;
dataI, dataF : string;
begin
// If an exception (unlikely, but on principle) happens in your VarArquivo
// assignment, then the original version will leak the allocated TStringList.
// Always place the TRY right after allocation of a memory block. That way
// you ensure that the FINALLY block will always release the allocated
// memory. Also, always include a FINALLY block to release the memory. Don't
// count on your code to reach the FreeAndNIL code (it doesn't in this
// instance, as you can see) to make sure that you actually release the
// memory.
VarArquivo := System.IOUtils.TPath.GetDocumentsPath + PathDelim + 'Limit.txt';
TextFile := TStringList.Create;
try // - except
try // - finally
TextFile.LoadFromFile(VarArquivo);
text := TextFile.Text;
// ShowMessage(TextFile.Text); // there is the text
// ShowMessage(text); // there is the text
dataI := FormatDateTime('yyyy/mm/dd', Date);
dataF := FormatDateTime('yyyy/mm/dd', StrToDate(text));
ShowMessage(dataF +' data f');
ShowMessage(dataI +' data I');
if ( dataF < dataI ) then
begin
ShowMessage('data F low');
end
else
begin
ShowMessage('data F high');
F_inicio.Show;
end;
finally
FreeAndNil(TextFile);
end
except
// NEVER just "eat" an exception. Especially not while developing the
// application.
// Always either log the exception or show it to the user.
on E:Exception do ShowMessage('Exception '+E.ClassName+': '+E.Message+#13#10+
'need login');
end;
end;
Now - if you do this, what exception and error message is shown. This is needed in order to properly diagnose the error. Perhaps you can even figure it out for yourself when you see what exactly goes wrong...
Hey can anybody help me with this error please, I can't seem to find a solution.
Any help would be appreciated.
I am working with Windows 8 and Delphi RAD Studio 2010.
If there are more errors then what I'm referring to please feel free to comment on them.
procedure TfrmStats.FormShow(Sender: TObject);
begin
// // Code that connects the TADOConnection to the database
// //conDatabase.Close;
// conDatabase.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=E:\[Phase 2]\db_DatabaseStock.mdb;Persist Security Info=False' ;
// conDatabase.Open;
// Code sets radiobutton.checked and checkbox.checked to true to avoid errors and
// simplify GUI
rb2D.Checked := True;
chkShowLegend.Checked := True;
// Code hides ShowGrid checkbox
chkShowItemGrid.Visible := False;
//Code hides Stringgrid
SGStats.Visible := False;
DrawPie;
**end;** // The breakpoint is here (Where delphi says the error is)
I will also show the code for the procedure being called:
procedure TfrmStats.DrawPie;
var
sSQL :string;
iRow, iCol, iA : Integer;
dblGT, dblLST, dblValue, PiePercentage : Double;
begin
// Procedure used to draw the chart of data
// Here call Subtotal
SGstats.Cells[0,1] := 'Sub Total';
SGstats.Cells[1,1] := IntToStr(GetSub);
with qryItems do
begin
// Select itemname an populate the stringgrid
SQL.Clear;
sSQL := 'SELECT DISTINCT ItemName FROM tblItems ORDER BY ItemName';
SQL.Add(sSQL);
Open;
Active := False;
Active := True;
if (RecordCount <> 0) then
begin
SGstats.RowCount := SGstats.RowCount + RecordCount;
for iRow := 0 to RecordCount -1 do
begin
SGstats.Cells[0,iRow+2] := FieldByName('ItemName').AsString;
Next;
end;
end;
end;
qryItems.Close;
with qryItems do
begin
// Select itembookquantity and populate the stringgrid
SQL.Clear;
sSQL := 'SELECT DISTINCT ItemName, ItemBookQuantity FROM tblItems ORDER BY ItemName';
SQL.Add(sSQL);
Open;
Active := False;
Active := True;
if (RecordCount <> 0) then
begin
SGstats.RowCount := SGstats.RowCount + RecordCount;
for iRow := 0 to RecordCount -1 do
begin
SGstats.Cells[1,iRow+2] := FieldValues['ItemBookQuantity'];
Next;
end;
end;
end;
// Code that actually draws piechart
with chtStats do
begin
//Clear the charts series
while (SeriesCount> 0) do
Series[0].Free;
//Change title
Title.Text.Clear;
Title.Text.Add('Items');
// Add series to piechart
AddSeries(TPieSeries.Create(Self));
Series[0].Name := 'PieItems';
for iRow := 2 to SGstats.RowCount -2 do
begin
PiePercentage := (StrToFloat(SGstats.Cells[1,iRow])/StrToFloat(SGstats.Cells[1,1]))*100;
Series[0].Add(StrToFloat(SGstats.Cells[1, iRow]), SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
end;
end;
The subtotal is supposed to be an integer. I'm also experiencing an 'Authentication Failed' error when running the program, any assistance would be appreciated. I'm still only a beginner so I may overlook small things or make simple mistakes :D
If I need to add more information to help, please let me know!
The error message is very clear: you are trying to convert to float an empty string. Delphi raises an exception because an empty string doesn't represent any valid float value.
You need to first check that the strings that you are using are not empty, and decide what to do in that case : inform the user, draw an empty pie, ...
By the way, if you want to consider your empty strings as zeros, then you can code your own customized conversion function.
function CustomStrToFloat(string: variant): double;
begin
if (string = null) or (Trim(string) = '') then Result := 0
else Result := StrToFloat(string);
end;
Please notice that this function will still raise an exception if your input is not an empty string (or a null variant), so the user will know that you are receiving inconsistent inputs.
Now you just have to change your code in order to use your customized conversion function
...
PiePercentage := (CustomStrToFloat(SGstats.Cells[1,iRow])/CustomStrToFloat(SGstats.Cells[1,1]))*100;
Series[0].Add(CustomStrToFloat(SGstats.Cells[1, iRow]), SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
...
About the 'Authentication Failed' error, can you debug your code and check what line raises that error ?. Looks like that it's going to be when you execute your SQL query, in that case the credentials that you have defined on the connection object of your SQLQuery are not correct.
EDIT: As Remy Lebeau has suggested, Delphi already includes two functions to deal with conversions from strings not containing valid representations of floating values. The first one is StrToFloatDef (string to float with a default value for non-valid strings).
You will only need to change your code to :
...
PiePercentage := (StrToFloatDef(SGstats.Cells[1,iRow],0)/StrToFloatDef(SGstats.Cells[1,1],0))*100;
Series[0].Add(StrToFloatDef(SGstats.Cells[1, iRow],0), SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
...
I don't use it because it will not only consider as zeros all the empty strings, but also every other string with inconsistent contents, and in those cases I prefer to let the program raise an exception, so the user is going to be notified that the input values are not valid.
The other function that you could use is TryStrToFloat, that is going to try to do the conversion and return true or false if the conversion has been successful.
If you use this, you will need to change those two lines to :
var FirstCell, SecondCell: extended;
...
...
FirstCell := 0;
SecondCell := 0;
if not TryStrToFloat(SGstats.Cells[0,iRow], FirstCell) then
ShowMessage('Input Values not valid');
if not TryStrToFloat(SGstats.Cells[1,iRow], SecondCell) then
ShowMessage('Input Values not valid');
PiePercentage := (SecondCell/FirstCell)*100;
Series[0].Add(SecondCell, SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
...
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I'm using Delphi 2006, Indy 10 (ver. 4957), IMAP4.
I would like to download an e-mail message, store it and some weeks later I would like to recreate it in a different folder. (It is sort of archiving and restoring it, so simple moving between folders does not work as I will delete the original message.) I download the message, store it, then make a copy of it with AppendMsg.
It works until that point when I check the target Temp2 folder, where most of the messages contain
This is a multi-part message in MIME format
unit Mail_Test;
interface
uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
StdCtrls;
type
TForm1 = class( TForm )
memLog: TMemo;
btn1: TButton;
procedure btn1Click( Sender: TObject );
private
procedure Log( LogMsg: string );
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
IdIMAP4,
IdMessage,
IdExplicitTLSClientServerBase,
IdSSLOpenSSL;
{$R *.dfm}
procedure TForm1.btn1Click( Sender: TObject );
var
IMAPClient: TIdIMAP4;
UsersFolders: TStringList;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
res: Boolean;
i: integer;
inbox, currUID: string;
cntMsg: integer;
msg, msg2: TIdMessage;
BodyTexts: TStringList;
flags: TIdMessageFlagsSet;
fileName_MailSource, TmpFolder: string;
begin
IMAPClient := TIdIMAP4.Create( nil );
try
OpenSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create( nil );
try
IMAPClient.Host := 'imap.gmail.com';
IMAPClient.Port := 993;
IMAPClient.Username := '....#gmail.com';
IMAPClient.Password := '....';
if Pos( 'gmail.com', IMAPClient.Host ) > 0 then begin
OpenSSLHandler.SSLOptions.Method := sslvSSLv3;
IMAPClient.IOHandler := OpenSSLHandler;
IMAPClient.UseTLS := utUseImplicitTLS;
end;
try
res := IMAPClient.Connect;
if not res then begin
Log( ' Unsuccessful connection.' );
exit;
end;
except
on e: Exception do begin
Log( ' Unsuccessful connection.' );
Log( ' (' + Trim( e.Message ) + ')' );
exit;
end;
end;
try
UsersFolders := TStringList.Create;
try
res := IMAPClient.ListMailBoxes( UsersFolders );
if not res then begin
Log( ' ListMailBoxes error.' );
exit
end;
except
on e: Exception do begin
Log( ' ListMailBoxes error.' );
Log( ' (' + Trim( e.Message ) + ')' );
exit;
end;
end;
Log( 'User folders: ' + IntToStr( UsersFolders.Count ) );
for i := 0 to UsersFolders.Count - 1 do begin
Log( ' [' + inttostr( i + 1 ) + '/' + inttostr( UsersFolders.Count ) + '] Folder: "' + UsersFolders[ i ] + '"' );
end;
IMAPClient.RetrieveOnSelect := rsDisabled;
inbox := 'INBOX';
Log( 'Opening folder "' + inbox + '"...' );
res := IMAPClient.SelectMailBox( inbox );
cntMsg := IMAPClient.MailBox.TotalMsgs;
Log( 'E-mails to read: ' + IntToStr( cntMsg ) );
// res := IMAPClient.RetrieveAllEnvelopes( AMsgList );
msg := TIdMessage.Create( nil );
msg2 := TIdMessage.Create( nil );
BodyTexts := TStringList.Create;
TmpFolder := 'c:\';
res := IMAPClient.CreateMailBox( 'Temp2' )
try
for I := 0 to cntMsg - 1 do begin
Log( ' [' + inttostr( i + 1 ) + '/' + inttostr( cntMsg ) + '] E-mail...' );
IMAPClient.GetUID( i + 1, currUID );
Log( '(Downloading message...)' );
IMAPClient.UIDRetrieve( currUID, msg );
fileName_MailSource := TmpFolder + 'Log_Mail_' + currUID + '.eml';
msg.SaveToFile( fileName_MailSource, false );
// In the final version I will delete the original message
// so I have to recreate it from the archived file
msg2.LoadFromFile( fileName_MailSource );
res := IMAPClient.AppendMsg( 'Temp2', msg2, msg2.Headers, [] );
end;
finally
FreeAndNil( msg );
FreeAndNil( msg2 );
FreeAndNil( BodyTexts )
end;
finally
IMAPClient.Disconnect;
end;
finally
OpenSSLHandler.Free;
end;
finally
IMAPClient.Free;
end;
end;
procedure TForm1.Log( LogMsg: string );
begin
memLog.Lines.Add( LogMsg );
Application.ProcessMessages;
end;
end.
You are calling the version of AppendMsg() that lets you specify alternative email headers. In just about every situation I can think of, you will never want to do that (I don't even know why TIdIMAP4 exposes that functionality).
The reason is because AppendMsg() saves the TIdMessage to an internal TStream and then sends the email body from that TStream to the server. If you specify alternative headers, they will be sent as-is and not match the header data that was used to create the email body. Most importantly, the MIME boundary used to separate MIME parts within the email body will not match the boundary specified in the headers that are actually sent to the server, which would account for the symptoms you are seeing. That boundary value is randomly generated by TIdMessage whenever it is encoded, so it is not available in the TIdMessage.Headers property prior to calling AppendMsg().
So, with that said, I strongly suggest you change your code to set the AAlternativeHeaders parameter of AppendMsg() to nil (or use the overloaded version of AppendMsg() that does not have an AAlternativeHeaders parameter at all) so that AppendMsg() will send the actual headers that TIdMessage itself generates when it is encoded prior to upload:
res := IMAPClient.AppendMsg( 'Temp2', msg2, nil, [] );
Or:
res := IMAPClient.AppendMsg( 'Temp2', msg2, [] );
For an iPhone Firemonkey application I am storing files (images) in the 'tmp' folder and using them in my application. I want to be able flush the cache by deleting say all of the '.jpg' files on demand, but I cannot seem to programatically match them in a FindFirst() call.
I am using a simple FindFirst() / FindNext() / FindClose() loop to list (and delete) the contents of a folder.
Under windows the code works perfectly. The same application under iOS (iPhone) is always returning a value of -1 (error) for the FindFirst() call, and SearchRec.Name is blank. I have tried using various file patterns including '.' with no success.
I know the files exist because I can read and write to them (under both iOS and windows) without error, and their contents is correct. A FileExists() check also returns True.
Also, if I specify a file pattern with no wildcard, to check for a known file (which really isn't the point of a FindFirst() call), the call never returns (again this is fine under windows)!
Has anyone had any success with this under iOS and can offer any thoughts?
Thanks,
EDIT: Code snippet as requested which demonstrates the problem.
Note: _sFolderName contains the cache folder name which I have confirmed is definitely correct.
function GetCacheFileList : string;
var
iResult: integer;
sr: TSearchRec;
sPath,
sTemp: string;
sFilename : TFilename;
begin
sTemp := '';
sFilename := _sFolderName + '*.jpg';
//
iResult := FindFirst(sFilename, faAnyFile, sr); // ALWAYS RETURNS -1 under iOS
while (iResult = 0) do
begin
sTemp := sTemp + sr.Name + sLineBreak;
iResult := FindNext(sr)
end; { while }
//
FindClose(sr);
Result := sTemp
end;
I don't know how well FindFirst, etc are supported on non-Windows platforms, but I do recall someone from the Delphi team saying once that the routines in the IOUtils unit are specifically designed to make file I/O work right for cross-platform coding. Have you tried using the file search methods on TDirectory?
I don't know if Delphi XE 2 is shipped with headers from iOS SDK, but you can generate them for FreePascal(read here). And then use this method via standard API:
{$modeswitch objectivec1}
uses
iPhoneAll, CFBase, CFString;
type
TFileList = record
Count : Integer;
Items : array of String;
end;
procedure file_Find( const Directory : String; var List : TFileList; FindDir : Boolean = FALSE );
var
i : Integer;
fileManager : NSFileManager;
dirContent : NSArray;
path : NSString;
fileName : array[ 0..255 ] of Char;
error : NSErrorPointer;
isDirectory : Boolean;
begin
fileManager := NSFileManager.alloc().init();
path := NSString( CFStr( PChar( Directory ) ) );
dirContent := fileManager.contentsOfDirectoryAtPath_error( path, error );
List.Count := 0;
fileManager.changeCurrentDirectoryPath( path );
for i := 0 to dirContent.count() - 1 do
begin
if FindDir Then
begin
if ( fileManager.fileExistsAtPath_isDirectory( dirContent.objectAtIndex( i ), #isDirectory ) ) and ( not isDirectory ) Then continue;
end else
if ( fileManager.fileExistsAtPath_isDirectory( dirContent.objectAtIndex( i ), #isDirectory ) ) and ( isDirectory ) Then continue;
SetLength( List.Items, List.Count + 1 );
FillChar( fileName[ 0 ], 256, 0 );
CFStringGetCString( CFStringRef( dirContent.objectAtIndex( i ) ), #fileName[ 0 ], 255, kCFStringEncodingUTF8 );
List.Items[ List.Count ] := PChar( #fileName[ 0 ] );
INC( List.Count );
end;
fileManager.dealloc();
end;
This function returns record TFileList with array of all found files(or directories). Then you can just pars names of files and do something with jpg-files.
This has been fixed under XE2 update 3