Jump to content

Mark Williams

Members
  • Content Count

    274
  • Joined

  • Last visited

Posts posted by Mark Williams


  1. I have tested with an email from September when UK was + 1 hour ahead of UTC. I get the following results:

     

    IDMessage1.Date =25/09/2020 08:31:34 

    Date header: Fri, 25 Sep 2020 09:31:34 +0100

     

    Both parties in UK. The email was received by me just after 09:31 local time. Of course, that doesn't mean it was sent at 9.31. It could have been sent at 8.31 I suppose.

     

    I just changed the clock on my pc to a date where UTC different from local time and I then sent myself an email. The results were:

     

    IDMessage.Date=14/08/2020 09:02:26

    Date Header: Fri, 14 Aug 2020 10:02:26 +0100 

     

    The pc clock was at 10:02 when I sent the email. So the date header is showing the local time. IDMessage date seems to me to be showing UTC time.

     

     

    The function for reading the date header in IDGlobalProtocols is:

     

    function GMTToLocalDateTime(S: string): TDateTime;
    var
      DateTimeOffset: TDateTime;
    begin
      if RawStrInternetToDateTime(S, Result) then begin
        DateTimeOffset := GmtOffsetStrToDateTime(S);
        {-Apply GMT and local offsets}
        Result := Result - DateTimeOffset;
        Result := {$IFDEF HAS_UniversalTimeToLocal}UniversalTimeToLocal(Result){$ELSE}Result + OffsetFromUTC{$ENDIF};
      end;
    end;

    As I read it, it is converting the date header to a datetime and then deducting the value of the DateTimeOffSet. As the date header (at least in my case) is showing the local time in any event, deducting the offset is going to produce the UTC time. 

     

    Am I misunderstanding something? 

     


  2. Easy solution. Just read an older email when UK time was not aligned with UTC!

     

    The answer for anyone interested is that TIDMessage's date property shows the UTC time.


  3. I want to extract the UTC  date time that an email was sent.

     

    I cannot work out whether I can just read TIDMessage's Date property or if I have to read the Date header. I am in a time zone that is currently aligned with UTC so I can't tell whether the Date property is UTC or local!


  4. Just now, Remy Lebeau said:

    Because it is still being used by the IDE. 

    So presumably I would have to reinstall INDY every time I install a new version of Delphi?

     

    Is there anything wrong with my alternative solution ie copying IDCoderTNef.pas to a separate folder along with IdCompilerDefines.inc. This compiles, although I have not yet tested if it works as I don't have any winmail.dat files.


  5. 57 minutes ago, Remy Lebeau said:

    That would require adding the unit to Indy's Protocols package, and then recompiling and reinstalling Indy.

    I tried that route. Added to the protocols folder and then tried to compile IndyProtocols270.bpl. Got an error "Cannot compile package 'IndyProtocols270' which is currently required by Delphi 10.4"


  6. Adding TIDCoderNef.pas to the protocols folder installed with Delphi doesn't work. Compiling fails to locate the unit.

     

    Saving it in a different location causes compile to fail whilst trying to find IDCompilerDefines.inc. I have copied the latter from protocols folder. to my new location, which compiles fine. However, is this the right approach or should I be dealing with this in a different way?


  7. 4 hours ago, Remy Lebeau said:

    TIdCoderTNEF was introduced in Indy 10, are you looking at Indy 9, perhaps?

    Quote

    No. Definitely version 10. I have a host of IDCoder... units in the protocols folder, but no TIDCoderTNEF.

     

    Indy was installed same time as I installed Delphi 10.4. Just been back over the last couple of installs 18.0, 19.0, 20.0 and latest is 21.0. IDCoderTNef is not in the protocols folder of any of these installs.


  8. 2 hours ago, Anders Melander said:

    So you are doing it with Outlook which again prompts me to ask why you're not also handling the eml files the same way (after importing them into Outlook) instead of handling them in two different ways?

    I don't want to import the emails into Outlook as it is an unnecessary and added overhead (it's also not as straightforward to do so for EML files as for MSG files). 

     

    My app needs to deal with EML files on a folder in Explorer. I can read the properties I need quickly using TIDMessage. I then post that data and the file to the server/database. I don't need the overhead of loading them into Outlook before doing this.

     

    If TIDMessage could handle MSG files I'd do it the same way. However, it doesn't so I am considering using Outlook to do it for me. But that comes at a cost. So it may be better for me to do as you suggest and use StgOpenStorage just for MSG files. Haven't quite made my mind up yet.


  9. In case anyone is interested, it is fairly straightforward to open a MSG file in an Outlook MailItem (if you know how). Code below (thanks to Dmitry Streblechenko posted on StackOverflow):

    var
        App, NS, Msg:Variant;
    begin
          App := CreateOLEObject('Outlook.Application');
    
          NS := App.GetNamespace('MAPI');
          NS.Logon;
          Msg := NS.OpenSharedItem(fileName);

    I've also found a VB example of how to import EML files into Outlook: https://www.howto-outlook.com/howto/import-eml-files.htm. This shows how to import them into an Outlook folder.


  10. Just now, Anders Melander said:

    Yes I understood that. I was talking about your statement : "drop the msg files into Outlook and handle them there somehow".

    I am looking to handle email messages saved to disk in whatever format. I need to open the emails, read certain properties from them and then post the file and the data to a database. If I can do this with just the file (as I can with EML using TIDMessage) then there is no need to fire up Outlook and copy the files into Outlook and then process them.


  11. 2 hours ago, Anders Melander said:

    If I'm understanding what you're saying you are going drop the msg files into Outlook and handle them there somehow. If that's an option then why not also handle the eml files this way?

    I am processing the email files for upload to a server. I don't need to add them to Outlook to do this. If I can open and handle them via TIDMessage it will save a significant amount of time.


  12. 10 hours ago, Remy Lebeau said:

    No, it does not, only emails in RFC822 format, like .EML files.

    OK. Thanks for the info.

     

    It didn't throw up an exception when trying to open the .msg file. 

     

    I have decided that possibly the best way to proceed is to use TIDMessage to open and read any emails in the right format and Outlook for any emails TIDMessage can't handle by adding the latter to a temp folder in Outlook, processing and then removing the temp folder (subject to me working out exactly how I do that!).

     

    If you or anyone else has any alternative suggestions before I start coding this, they would be really appreciated.


  13. 1 hour ago, Anders Melander said:

    You can use the IStorage interface to load the file (via IStream)

    I was hoping to find a one-size fits all solution. Off hand do you happen to know if most other email types are also structured storage files?

     

    1 hour ago, Anders Melander said:

    AFAIR there's also an OpenIMsgOnIStg API function that can do some of the work for you but I think it might require that Outlook is installed.

    Requiring Outlook wouldn't be an issue, but I think it's MAPI that's needed. I've had a look at the Microsoft docs this returns me an IMessage on top of the storage doc. I have also had a look for some examples of how to deploy OpenIMsgonIStg. Haven't found much on it yet and suspect it will take me some time (form me) to master.  Do you (or anyone) happen to know know if the IMessage interface will work with other email types or just Outlook? 

     

    Before I go down that route, I would really appreciate any feedback on whether TIDMessage can handle MSG files and, if so, how. @Remy Lebeau


  14. Sorry if I didn't make my requirements clear.

     

    My app needs to be able to read emails in (hopefully) almost any format that have been dropped into an explorer folder. In my case I dropped them from Outlook so they were saved as .msg. Could be .eml format etc. I don't have control over the email source from which my users will dropping emails into Explorer.

     

    Basically, I wish to read the various properties of the email (From, To, CC, BCC, Subject, body, MessageID etc) and add them to a database.

     

    But to do that I need to be able to load the emails from a file rather than outlook. And that's where I am getting stuck.

     

    By way of update I have just saved an email from GMail to disk (so eml format) and that was read ok by TIDMessage save for the body part, which I assume requires me to read the messageParts.

     

    So with TIDMessage the issue seems to be with msg files.

     

    As I originally said, I am not particularly bothered how I do this as long as whichever method I adopt it can reliably open and read most email formats.


  15. I am trying to work out the best way to batch read emails dropped into a windows explorer folder. I can think of three possibilities, but I can't get any of them to work!

     

    MAPI & IMessage

    This would probably do the job save for the fact that I cannot figure out how to open a file from explorer to use with the IMessage interface.

     

    Outlook

    An outlook MailItem would also probably do the job, but it has no LoadFromFile method and no other obvious method that I can find to load a file into a MailItem object.

     

    INDY TIDMessage

    I have tried with the following code:

     IDMessage1.LoadFromFile(FileName);
    
      with IDMessage1 do
            begin
              label6.caption := From.Text;
              label7.Caption := Recipients.EmailAddresses;
              label9.Caption := CCList.EmailAddresses;
              label10.Caption := BCCList.EmailAddresses;
              Label12.Caption := Subject;
              Label14.Caption := MsgId;
              Memo1.Text := Body.Text;
            end;

     

    Filename is a msg file dropped from outlook into explorer.

     

    It loads without complaint, but the output is wrong. Eg

    From.Text returns

    Quote

    "W

    W is the correct initial for the sender.

     

    Recipients.EmailAddresses returns

    Quote

    "M

    Again M is the correct initial.

     

    Subject returns

    Quote

    R

    This time with no leading double quote.

     

    I assume I am missing something, but I do not know what and I can find no helpful examples.

     

    I am looking for something that can reliably read emails in most formats. Don't know if TIDMessage is designed for this purpose. 

     

    Hopefully someone can help re any of the above or even suggest any other possible solutions.

     


  16. I want to use the IPropertyStore interface to read media file tags. I couldn't find any Delphi examples of how to do this, but I did find a C++ example:

    C++ Example

     

    I am not familiar with C++, but using this example I have got working Delphi code:

     

     var
        Store: IPropertyStore;
        v: PropVariant;
    begin
      if openDialog1.Execute then
        begin
          CoInitialize(Nil);
          SHGetPropertyStoreFromParsingName(pWideChar(openDialog1.FileName),
          Nil, GPS_READWRITE, IPropertyStore, store);
          store.GetValue(PKEY_Music_AlbumTitle, v);      
          Showmessage(v.bstrVal);
          Couninitialize;
        end;
    end;

    This works without complaint (needs the following added to the uses clause WinApi.ShlOBj,  WinAPI.ActiveX,  WinAPI.PropKey,   WinAPI.PropSys;).

     

    The C++ example says there is a very important undocumented function: store->release().

     

    The IPropertyStore does not have this function. 

     

    Does anyone know what if anything I need to do to clean up the IPropertyStore in Delphi after use?

     


  17. 17 hours ago, Dany Marmur said:

    Since you do not state what you are trying to achieve... it's a bit difficult.

    I though I stated that I was trying to read the BuiltInDocumentProperties of office documents and that I was having difficulties reading certain of those properties. I also provided the code and stated what were the problems I was experiencing. I'm not sure what further info was relevant.

     

    I wish to proceed, if at all possible (and I don't see why it shouldn't be) with OLE late binding to open the office documents (I need to save them in pdf format) and then read their BuiltInDocumentProperties whilst I have the document open. I appreciate your recommendation of OOXML, but I don't really have time to delve into this at the moment and would prefer to use BuiltInDocumentProperties, particularly as I have to open the document via its relevant MS app in any event.


  18. I am using late binding to open Word/Excel documents etc to try to read the built in and custom properties if documents/workbooks.

     

      var
    	MSApp, Props, item:Variant;
    	i, int:integer;
    	d:TDateTime;
    	f:Double;
    	s:string;
    begin
    	MSApp := CreateOLEObject('Word.Application');
    	MSApp.Documents.OpenNoRepairDialog(filename, false, true, false, password, password,
                False, password, password, emptyParam, emptyParam, false, true);
    	
    	MSApp.ActiveDocument.Repaginate;
    	
    	Props := MSAPP.ActiveDocument.BuiltInDocumentProperties;
    	
    	for i := 0 to Props.Count do
    		begin
    			item := Props.item[i];
    			memo1.Lines.Add('Name='+item.Name);
    						
    			case varType(item.value)  of
              		varSmallInt, varShortInt,  VarInteger, varSingle, varByte, varWord, varLongWord, varInt64:
                		begin
                  			int:=item.value;
                  			Memo1.Lines.Add('value as int='+int.ToString);
                		end;
              		varDate:
                		begin              
                  			d:=item.value;              
    						Memo1.Lines.Add('value as date='+DateTimeToStr(d));
                		end;
              		varDouble, varCurrency:
                		begin
                  			f:=item.value;
                  			Memo1.Lines.Add('value as float='+FloatToStr(f));
                		end;
              		varBoolean:
                		begin              		
                  			b:=item.value;
                  			Memo1.Lines.Add('value as boolean='+ord(b).ToString);
                		end;
              				varOleStr, varStrArg, varString:
                				begin              					
                  					s:=item.value;
                  					Memo1.Lines.Add('value as string='+s);
                				end
              		else
                		try              			
                  			s:=item.value;
                  			Memo1.Lines.Add('value as somethign else='+s);
                		except
                		end;
            	end;
    			
    		end;

    I am getting errors and other issues with a number of the properties:

     

    DateTime

    There are three date time properties in the built in properties:

    Last print date

    Creation date

    Last save time

     

    My function fails with the print date and the last save time. I get an EOLEExecption "unspecified error" when querying the VarType of the item value (case varType(item.value) ).

     

    I don't get an error for Creation date, however, the creation date is always read as the current date time, regardless of what the properties of the document actually says.

     

    The format of all three dates appears identical in the document properties.

     

    Pages, word count ,number of characters  etc

    Pages always returns 1 regardless of number of pages. I thought calling Repaginate first might resolve this, but it didn't.

     

    Word & character count always return 0 (which is incorrect).

     

    Any help with the above appreciated. Thanks.

     


  19. AFAIK you can't change the colour or event the font colour of a standard TButton. You can derive your own button with a canvas (which I have done in the past), but its quite a lot of work and won't work decently with themes.

     

    Francois' suggestion of a timer is sound, but I would suggest internittently enabling/disabling the button to provide a flash effect/

    • Like 1

  20. My dll calls the function shown in my original post to change the database as required (it needs to have access to 3 databases on the same server).

     

    If I call my dll using the external ip (eg https://52.132.222.67/MyDLLLocation/MyDLLName/MyDLLFunction) all works okay. The moment I substitute the external ip with an internal ip (192.168.0.12) I get the cannot change definition error.

     

    I really want to avoid re-establishing a connection almost every time the dll is called. 

     

    There are a number of things I do not understand and would like to understand:

     

    1. Why is this happening at all? Is it because the pooled connection somehow thinks that its server property is being changed?
    2. Why does the pooled connection think there is an associated connection in one instance (ie where local ip used), but doesn't when an external ip is used?
    3. Is this to do with localhost being the server and, if so, how do I get around this?
    4. If my only solution is as @Dmitry Arefievsuggests to use the "full object" can someone please tell me how to do that? Also, does it avoid the problem of re-establishing the connection each call?

    But I would still like to understand why I am getting this different behaviour for external and internal ips.

×