Check unread messages with Indy - delphi

I'm doing just for fun an unread messages checker application in Delphi. I'm using Indy 10. I can connect with Gmail and can retrieve all the messages but I'm facing a problem here: I cannot tell if a message is already read or not. There is a flag property in the TidMessage component that should tell me if the message has been read.
The code looks like this:
procedure TForm1.btTestConnectionClick(Sender: TObject);
var
i: Integer;
count: Integer;
flag: TIdMessageFlags;
begin
if (pop3Test.Connected) then begin
pop3Test.Disconnect;
end;
pop3Test.Username := edAccount.Text;
pop3Test.Password := edPassword.Text;
pop3Test.Host := HOST;
pop3Test.AuthType := patUserPass;
pop3Test.Port := PORT;
pop3Test.Connect;
Count := 0;
for i := pop3Test.CheckMessages downto 1 do begin
pop3Test.Retrieve(i, IdMessage1);
if (mfSeen in IdMessage1.Flags) then begin
Count := Count + 1;
end;
end;
ShowMessage(IntToStr(Count));
pop3Test.Disconnect;
end;
In the test mailbox there is one unread message but all the retrieved messages have the flags enum property empty so the result is always 0. Am I doing something wrong? Is it a problem of Indy/Gmail compatibility?
Thanks.
EDIT: I'm definitely doing something wrong as testing with a Hotmail account shows the same empty-flags property problem.

the POP3 protocol does not support Message state information on the server like read, replied to, or deleted . try using IMAP for Gmail instead.

The best (and quickest) way to find this answer would be to search the Indy sourcecode for "mfSeen" You should find it only utilized in idIMAP* units. RRUZ is correct - POP3 doesn't offer this inherent ability. In POP3 you need to track this on the client side. This flag was added to IdMessage for IMAP purposes, and not necessarily for POP3.
TIdMessageFlags should likely have been named TIdIMAPMessageFlags

Related

TCPServer.Contexts.LockList : is it safe to do that?

Until now, I was calling the following function every second :
function TForm1.ListeConnecteMaj():Boolean;
var
i : integer;
List : TIdContextList;
Client : TSimpleClient;
begin
List := TCPServer1.Contexts.LockList;
try
NbSConnect := List.Count;
for i := 0 to List.Count -1 do
begin
Client := List[i];
..... // getting information from Client
end;
finally
TCPServeur1.Contexts.UnlockList;
end;
end;
As I need to support hundreds of simultaneaous connections, I want to reduce as much as possible the duration of the LockList.
I've tried this. It works but is it really safe ?
function TForm1.ListeConnecteMaj():Boolean;
var
i : integer;
List : TIdContextList;
Client : TSimpleClient;
begin
List := TCPServer.Contexts.LockList;
TCPServeur.Contexts.UnlockList;
try
NbSConnect := List.Count;
for i := 0 to List.Count -1 do
begin
Client := List[i];
..... // getting information from Client
end;
finally
end;
end;
It works but is it really safe?
No, it is not thread-safe.
You can safely use List reference acquired by LockList until you call UnlockList. The moment you call UnlockList, list will no longer be protected and any List access after that point can cause concurrency issues.
Your original code is the proper way to use LockList/UnlockList.
As #DalijaPrasnikar's answer explains, your proposal is not safe, no. As soon as the list is unlocked, the server is able to freely modify the contents of the list as clients connect and disconnect, which will cause concurrency issues with your code.
I would suggest a different approach - rather than polling the client list every second, I would use the OnConnect and OnDisconnect events to add/remove elements from your UI as needed whenever the client list changes. If you are trying to track status updates per client during socket operations, you can post notifications to the main UI thread as they happen.
Thanks for your answers.
Following this CheckQueue exemple would it be possible to do the following :
replace " List := TCPServer.Contexts.LockList;" with " List.Assign(TCPServer.Contexts.LockList); "

IP*Works! SearchMailbox for IMAPS returns all available emails, even unmatching

I am using IP*Works! V9. I try to restrict the returned emails to only the one matching a restriction using SearchMailbox. My code looks like this:
lIMap.Mailbox := 'INBOX';
lIMap.SelectMailbox;
lIMap.CheckMailbox;
lIMap.Config('FETCHAFTERSEARCH=True');
lIMap.SearchMailbox('SUBJECT Diessenhofen UNSEEN');
if (lIMap.MessageCount > 0) then
begin
...
end;
MessageCount always reflects the total number of emails instead of one (there is one match in my inbox).
The IMAP server is Kereo
The documentation says it doesn't work like that. SearchMailbox doesn't restrict what's available to you, instead it calls a user-supplied function and fires an even once for each message in the search result.
Thanks to the answer of #arnt, I figured out a solution that works for me.
Yes, for every Message that corresponds to the search criteria, the event OnMessageInfo is fired.
Since I need to go through all messages in a loop, I ended up doing this:
procedure TReadIMapObjectsFavFktProperty.MessageInfo(Sender: TObject;
const MessageId, Subject, MessageDate, From, Flags: String;
Size:Int64);
begin
if (MessageList.IndexOf(MessageId) < 0) then
begin
MessageList.Add(MessageId);
end;
end;
where MessageList is a TStringList with delimiter ',';
I can then get all messages using either
lIMap.MessageSet := MessageList.Text;
again firing the same event or loop through them using the size of the MessageList like this:
for aa := 0 to MessageList.Count - 1 do
begin
lIMap.MessageSet := MessageList.Strings[aa];
lIMap.FetchMessageInfo;
...
end;

Delphi Indy Ping Error 10040

I have a small piece of code that checks if a computer is alive by pinging it. We use to have a room with 40 computer and I wanna check remotely through my program which on is alive.
Therefore I wrote a little ping function using indy
function TMainForm.Ping(const AHost : string) : Boolean;
var
MyIdIcmpClient : TIdIcmpClient;
begin
Result := True;
MyIdIcmpClient := TIdIcmpClient.Create(nil);
MyIdIcmpClient.ReceiveTimeout := 200;
MyIdIcmpClient.Host := AHost;
try
MyIdIcmpClient.Ping;
Application.ProcessMessages;
except
Result := False;
MyIdIcmpClient.Free;
Exit;
end;
if MyIdIcmpClient.ReplyStatus.ReplyStatusType <> rsEcho Then result := False;
MyIdIcmpClient.Free;
end;
So I've developped that at home on my wifi network and everthing just work fine.
When I get back to work I tested and I get an error saying
Socket Errod # 10040 Message too long
At work we have fixed IPs and all the computer and I are in the same subnet.
I tried to disconnect from the fixed IP and connect to the wifi which of course is DHCP and not in the same subnet, and it is just working fine.
I have tried searching the internet for this error and how to solve it but didn't find much info.
Of course I have tried to change the default buffer size to a larger value but it didn't change anything I still get the error on the fixed IP within same subnet.
Moreover, I don't know if this can help finding a solution, but my code treats exceptions, but in that case it takes about 3-4 seconds to raise the error whereas the Timeout is set to 200 milliseconds. And I cannot wait that long over each ping.
By the way I use delphi 2010 and I think it is indy 10. I also have tested on XE2 but same error.
Any idea
----- EDIT -----
This question is answered, now I try to have this running in multithread and I have asked another question for that
Delphi (XE2) Indy (10) Multithread Ping
Set the PacketSize property to 24:
function TMainForm.Ping(const AHost : string) : Boolean;
var
MyIdIcmpClient : TIdIcmpClient;
begin
Result := True;
MyIdIcmpClient := TIdIcmpClient.Create(self);
MyIdIcmpClient.ReceiveTimeout := 200;
MyIdIcmpClient.Host := AHost;
MyIdIcmpClient.PacketSize := 24;
MyIdIcmpClient.Protocol := 1;
MyIdIcmpClient.IPVersion := Id_IPv4;
try
MyIdIcmpClient.Ping;
// Application.ProcessMessages; // There's no need to call this!
except
Result := False;
Exit;
end;
if MyIdIcmpClient.ReplyStatus.ReplyStatusType <> rsEcho Then result := False;
MyIdIcmpClient.Free;
end;
For XE5 and Indy10 this is still a problem, even with different Packet Size.
To answer the more cryptical fix:
ABuffer := MyIdIcmpClient1.Host + StringOfChar(' ', 255);
This is a "magic" fix to get around the fact that there is a bug in the Indy10 component (if I have understood Remy Lebeau right).
My speculation is that this has some connection with the size of the receive buffer. To test my theory I can use any character and don't need to include the host address at all. Only use as many character you need for the receive buffer. I use this small code (C++ Builder XE5) to do a Ping with great success (all other values at their defaults):
AnsiString Proxy = StringOfChar('X',IcmpClient->PacketSize);
IcmpClient->Host = Host_Edit->Text;
IcmpClient->Ping(Proxy);
As you can see I create a string of the same length as the PacketSize property. What you fill it with is insignificant.
Maybe this can be of help to #RemyLebeau when he work on the fix.
use this code
ABuffer := MyIdIcmpClient1.Host + StringOfChar(' ', 255);
MyIdIcmpClient.Ping(ABuffer);

Outlook automation with Delphi - Queue

I currently have the following code:
while not (sqlMailMergeData.Eof) do
begin
if sqlMailMergeData.FieldByName('Email').AsString <> '' then
begin
Inc(Count);
{Connect to Outlook}
MailItem := OpOutlook1.CreateMailItem;
MailItem.MsgTo := sqlMailMergeData.FieldByName('Email').AsString;
MailItem.Body := Form48.Memo1.Text;
MailItem.Subject := Form48.Edit3.Text;
MailItem.Send;
end;
Form34.sqlMailMergeData.next;
end;
However Outlook prompts you to allow ever email with a delay of 5 seconds. Sending after the loop overwrite the same MailItem.
MailItem.Save;
Saves all the items to draft without prompting. This is not a bad solution and could be an extra feature but requires more user input to move the items to outbox.
Is there a function to send each mail item to the outbox? or should I consider creating a string of all the email address e.g.
MailItem.MsgTo := "example#email.com; example2#email.com"
Thanks
When working with outlook, you might consider using outlook redemption1, this way you can bypass the security prompt and send the mail directly from your code.
Then your only option is to do what Redemption (I am its author) is doing under the hood - use Extended MAPI.
This is the code which works fine for me:
Outlook := CreateOleObject ('Outlook.Application');
// Repet the code below for each mail:
OutlookMail := Outlook.CreateItem (olMailItem); // olMailItem = 0;
OutlookMail.Recipients.Add ('example#email.com').Resolve;
OutlookMail.Recipients.Add ('example2#email.com').Resolve;
OutlookMail.Subject := Form48.Edit3.Text;
OutlookMail.Body := Form48.Memo1.Text;
OutlookMail.BodyFormat := olFormatHTML;
// OutlookMail.SendUsingAccount := OutlookAccount; // If you need to select the acount
OutlookMail.Send;

Indy TIdImap4.UIDRetrieve method!

Here is my little code:
curMessage:TIdMessage;
tidImap: TIdIMAP4;
...
tidImap.UIDRetrieve('123', curMessage);
That works fine! Now when i try to read
curMessage.Body
Then it is empty sometimes. I've understand that it is empty when message IsMsgSinglePartMime is False. So then i can't read message's body from Body property.
I've searched in curMessage's every property, but nowhere could i found the body text. What makes it even more odd, is that when i save curMessage
curMessage.Savefile('...');
then i can see all the body there.
I don't want to make another request to fetch for the body (eg UIDRetrieveText(2)) because i understand that the body data is there somewhere, i just could not find it or is Savefile/SaveStream making some internal requests to server?
Thank you guys in advance!
You need to be checking TIdMessage.MessageParts.
var
Msg: TIdMessage;
i: Integer;
begin
// Code to retrieve message from server
for i := to Msg.MessageParts.Count - 1 do
begin
if (Msg.MessageParts.Items[i] is TIdAttachment) then
// Handle attachment
else
begin
if Msg.MessageParts.Items[i] is TIdText then
HandleText(TIdText(Msg.MessageParts.Items[i]).Body);
end;
end;
end;
In Indy 10, TIdMessageParts has been moved into it's own unit, so you may have to add IdMessageParts to your uses clause.

Resources