IBM®
Skip to main content
    Country/region select      Terms of use
 
 
   
     Home      Products      Services & solutions      Support & downloads      My account     

developerWorks  >  Lotus  >  Forums & community  >  Best Practice Makes Perfect

Best Practice Makes Perfect

A collaboration with Domino developers about how to do it and how to get it right in Domino

Nathan is right, "/" seems to work as a path delimiter on all operating systems that the Notes client runs on (current versions). Also, nobody seems to care if you have two consecutive path delimiters. So the PathDelimiter function is unnecessary; we can just write:

m_filepath = GetNotesTempDirectory() & "/" &  unik(0) & "." & suffix

I agree this should be a property of NotesSession, as should the OS temp directory path. I will propose this as a feature request.

Meanwhile, Brian Miller asks how we might get the Notes temp directory on AIX. Refer to this page on openntf.org which shows how to declare C API functions for several different operating systems.

There does not, however, seem to be an argument to Environ for each operating system that will return the OS temp directory for that OS. Here's a way that I believe will work.

Function GetSystemTempFolder As String
   Dim session As New NotesSession
   Select Case session.Platform
   Case "Macintosh", "Linux", "UNIX"
      GetSystemTempFolder = "/tmp"
   Case "Windows/32"
      GetSystemTempFolder = Environ("TEMP")
   Case Else
      Error ERR_UNSUPPORTED_PLATFORM, "GetSystemTempFolder: Unsupported platform '" & session.Platform & "'."
   End Select
End Function

For client work in general, it doesn't really matter whether you use the OS temp folder or the Notes temp folder because you have both folders to yourself. When you write code to run on a server, however, it's better to use the Notes folder. If there are multiple Domino partitions running on the server, each partition has its own Notes temp directory, but there's a common OS temp directory. Using the Notes temp directory gives less chance of filename conflicts. There's even less chance of a problem if you use a file name from @Unique as I did in my example, or create a folder with a name from the @Unique function, and put your files in there. Since Notes puts lots of files right in the Notes temp directory with names not chosen at random, it makes sense to avoid doing the same. For instance, if you want to temporarily detach a file from a document, rename it, and import it into a new attachment (as a way of renaming the attachment), it's best to do this in a folder where you're unlikely to encounter any files created by other code. Whatever you do, do not write temporary files into the default directory, which in most cases is the Notes program directory. This can result in the overwriting of important files that are part of your Notes client or server, involving you in difficult conversations with the administration and security staffs of your company.

Kerr wants to know what all this is in aid of, where I'm getting  base64 data, why I'm bothering to decode it, and what I do with it after. Kerr is nosy, but I guess I'm willing to say. One of the features of the Developer's toolkit I blogged about recently, is a selection to display the "raw" XML of a Composite Application design element or a Wiring Properties design element. So the output looks something like this (use right-click, View Image to see it full size).

Wiring properties XML dumpTo get the XML, I export the Wiring Properties design element into DXL, using a special little trick to make the exporter treat it as a file resource so that it converts the binary CD records of the $Filedata item back to the original file contents, which are base64 encoded as a text node inside a "filedata" element. I then have to decode the base64 data, which I can do using the NotesMimeEntity class. (It's possible to directly decode the CD records of the binary $FILEDATA item back into the original file data using LotusScript, but messy. Very messy.)

Of course, you could do this from the Designer menus by exporting the WSDL files to disk and then opening the files in your favorite XML editor, but this is a two-click operation to see the XML for all your Wiring Properties instantly.

BTW, I'm aware that viewing raw XML is not everybody's cup of tea, so I have an another tool for Wiring Properties that parses and summarizes the data, as shown below. For this operation, I don't need a stream with any particular character set, because our XML parsers don't pay any attention to the character set specification in the NotesStream anyway -- they just look at the bytes in the buffer, and figure out the character set from that, in the usual way of XML parsers everywhere. So the TempFileStream class is not needed in that case.

Report of Wiring Properties

Andre Guirard | 3 October 2007 03:35:40 PM ET | Plymouth, MN, USA | Comments (21)


 Comments

1) Trunky!
Kerr | 10/4/2007 5:24:16 AM

Nosy? That's a little pejorative isn't it? I'm just a little curious ;)

Anyway, thanks for the background, that's helped. I've been working on MIME entities, base64 encoding and charsets quite a bit and haven't come up against this use case. Always interesting to see what other people are doing.

2) Have you tried...
Kerr | 10/4/2007 5:33:50 AM

Andre, you mention that you don't need the temp file when you parse and summarise the data. For the raw XML display have you tried loading it via a parser, avoiding the temp file and just doing an identity transform or similar.

3) Loading it via a parser
Andre Guirard | 10/4/2007 8:44:03 AM

Kerr, thanks for the suggestion. You're thinking I could feed it to a DOM parser, then Serialize it into a NotesStream (so that it would actually be in Unicode character set, instead of just allegedly) then use ReadText to get the result.

That's a good idea and would work in some cases, maybe more efficiently than using the file. In this case, I think the reason people might be viewing the data is that there's something wrong with it. So maybe the XML is not valid, or maybe it matters which quote characters are being used (" vs. ') or how characters are encoded using &xxx; -- and Serializing would homogenize out those details.

Also, of course, not all text data that we get from base64 is XML -- this technique is also useful for reading the CSS text out of a Stylesheet design element, or text from any File Resource design element (such as the Properties lists used in 8.0).

4) Good point
Kerr | 10/4/2007 9:07:38 AM

Yeah, good point. I've just got this gut feeling that I can get the text from the base64 without having to use the temp file. I'll have to play with it and see what I can come up with.

5) Does this do the trick?
Kerr | 10/4/2007 11:58:06 AM

Hi Andre,

I've just had a play with this to see if I could recreate a similar scenario. What I've done is attach a UTF-8 encoded XML file to a document via the web, so that the whole notes doc is stored as MIME. At this stage there is no base64, but should be the same as if I'd created a MIME entity with base64 and decoded it. The MIMEEntity with the attachment has the following headers:

Content-Type: application/octet-stream

Content-Disposition: attachment

Content-Transfer-Encoding: binary

If I call getContentAsText on this entity I get back the XML, but not properly decoded from UTF-8. What I can do though is set the Content-Type MIMEHeader to include charset=UTF-8, and that fixes that problem.

Any use to you, or am I not simulating your scenario well enough?

6) re: Does this do the trick?
Andre Guirard | 10/5/2007 5:24:27 PM

So close! I played with this a while but had to give it up.

The problem is that some text files (in particular, CA XML created using the Blank Composite Application selection in Ctrl+N) have a byte-order mark on the beginning which causes the binary-to-utf-8 translation in Notes to get badly confused and start spewing out Chinese. I have to get the binary NotesStream, look for those bytes myself, and then set the start position past them before trying to translate the data to text.

So, unfortunately, I still don't have a way that doesn't require a temporary file.

7) re: Does this do the trick?
Kerr | 10/8/2007 7:29:36 AM

Urgh, there's always something!

It's odd though, UTF-8 shouldn't have a BOM at all. So if that's what's happening it's a bug somewhere. Also if that's the case, why does putting it in a temp file help? Won't be the first time Notes has stumped me with odd behaviour ;)

I'd probably need to see all the code to try and play with it further, so I'll chalk this one up in the "weird" column and leave it at that for now. Hopefully people have found this interesting :)

8) re: re: Does this do the trick?
Andre Guirard | 10/8/2007 11:50:01 AM

Kerr writes:

It's odd though, UTF-8 shouldn't have a BOM at all. So if that's what's happening it's a bug somewhere.
Based on wikipedia the BOM is optional with UTF-8, and many Windows program (e.g. Notepad) do put it in. So it seems like it's valid for it to be there (or at least, if it's not valid, it's outside our power to fix the many programs that might use it anyway).

Also if that's the case, why does putting it in a temp file help?

That's a multi-step reasoning process and I'm not certain it really is necessary so I won't reproduce it here. :-)

9) re: Does this do the trick?
Kerr | 10/8/2007 5:22:13 PM

Ah, yeah, it's not a BOM as such, as UTF-8 does not have any other possible ordering. I'd forgotten that UTF-8 can have those identifier bytes though, so not a bug, but doesn't explain why Notes is barfing on it.

As you say, nothing we can do about it though. An SPR? ;)

10) re: Does this do the trick?
Kerr | 10/9/2007 9:58:40 AM

I've just tried my original test with the UTF-8 "BOM" signature bytes, and it doesn't cause me any problems. There must be something else going on.

11) re: re: Does this do the trick?
Andre Guirard | 10/9/2007 11:24:31 AM

It sounds like the MAPI code to translate content into text does not have a problem with the BOM -- but the NotesStream code to do the same task, does. One would've thought they would be the same code, but apparently not.

Speaking of modularity, that's part of the reason I wanted the NotesStream to work for this. I don't like to be getting the XML data one way for one purpose, and another way for another purpose. It needs to be a stream for DOM parsing, and a stream is (supposedly) acceptable if you want to look at the contents in other ways.

I guess I'll have to bite the bullet and make two methods -- one to return the file resource/WSDL/CAXML contents as a string, and another to return it as a stream. That should let me do away with the temp file. But there is still a problem in DXL encoding that can add an extra byte or two to the end of the base64 data; with a stream we can skip the last few bytes if they exceed the $FileSize number. I guess I could convert the base64 to bytes, read the data into a stream, see whether the stream is too long, and if it is, create another stream, copy the good part of the data into that, then feed the new stream back into the NotesMIMEEntity so that I can get the correct text back.

I'm beginning to think I'm actually spending too much time on this one little piece...

I did write a bug report for the issue with NotesStream and the DXL filedata bug -- the latter has been fixed already.

12) how do i use it on windows 64 bit os?
rharan | 8/18/2010 8:47:38 PM

Andre, How do i check the same on windows 64 bit OS?

Thanks,

rharan

13) Finding the TEMP folder of the OS
Andre Guirard | 8/24/2010 11:25:14 AM

@rharan, I don't know; what happens when you try the same as Windows 32?

14) The file I created worked well with Window but not with Mac
Mimi Pham | 5/8/2014 3:38:25 PM

Andre,

I'm using the code and experienced some issues with Mac users. Window users received mail and access the file nicely but for Mac users they can't get the file open.

Here is the file name it creates:

2014 Apr 28 17:49:26 501 94331 eclipse 438 0 /tmp/SpamReport.eml

And here is the file name it tries to read:

2014 Apr 28 17:49:28 501 94331 eclipse -1 2 /SpamReport.eml.dir

Would you please guide me how to change the code to have the name displayed consistenly?

I'm appreciate your help!

Mimi Pham

15) Using this function on Mac
Andre Guirard | 5/8/2014 4:50:05 PM

Mimi, it seems you're expecting me to debug you code without even showing me the code. All I showed was how to get the temp folder. What you do with that information, is up to you. All I can suggest is that you debug the code and see what the values of the variables are at different points. Oh, and use Option Declare if this is LotusScript.

16) Your code worked great .. only users’s MAC version is the issues
Mimi Pham | 6/4/2014 12:57:24 PM

Dear Andre: Thanks for your reply, I was out on different mission last month. Please forgive me didn't attach the LS while speaking of the problem. Your code is excellent with all Window users. It's also worked well with new version MAC users. However using MAC old version the temp file was not created at all so when mail get sent, there is no file found in my \tmp folder.

Please advise me how to handle old MAC version users?

I'm greatly appreciate your help.

Best - Myle

%REM

Agent Mail Embedded

Created Apr 7, 2014 by Myle Luong/myle/AmericanU

Description: Comments for Agent

%END REM

Option Public

Sub Initialize

Dim session As New NotesSession

Dim db As NotesDatabase

Dim doc As NotesDocument

Dim rtitem As NotesRichTextItem

Dim object As NotesEmbeddedObject

Set db = session.CurrentDatabase

Set doc = New NotesDocument( db )

Set rtitem = New NotesRichTextItem( doc, "Body" )

Set object = rtitem.EmbedObject _

( EMBED_ATTACHMENT, "",GetSystemTempFolder &"\SpamReport.eml")

doc.Form = "Memo"

doc.SendTo = "jsnyder@american.edu"

' doc.CopyTo = "ksandell@american.edu"

doc.BlindCopyTo = "myle@american.edu"

doc.Subject = "Spam mail Report " + Format(Date$, "Long Date")

Call doc.Send( False, True )

Call doc.Save( False, True )

End Sub

Sub Terminate

End Sub

Function GetSystemTempFolder As String

Dim session As New NotesSession

Select Case session.Platform

Case "Macintosh", "Linux", "UNIX"

GetSystemTempFolder = "/tmp"

Case "Windows/32"

GetSystemTempFolder = Environ("TEMP")

Case Else

Error ERR_UNSUPPORTED_PLATFORM, "GetSystemTempFolder: Unsupported platform '" & session.Platform & "'."

End Select

End Function

17) Cont.. my ls
Mimi Pham | 6/4/2014 1:14:47 PM

Andre: This is the LS using to set up the /tmp file from other agent: Sub Initialize

Dim s As New NotesSession

Dim db As NotesDatabase

Dim dc As NotesDocumentCollection

Dim body As NotesItem

Dim rtitem As NotesRichTextItem

Dim mimebits As Variant

Dim n As Integer

Dim msgid As Variant

crlf = Chr(13) & Chr(10)

'Dim mime As NotesMIMEEntity, mime2 As NotesMIMEEntity

'** this is a form that has a rich text field that is set to store contents

'** in MIME format

CONVERT_FORM = "MimeConvert"

'** this is the field on the form mentioned above that stores rich text

'** as MIME

CONVERT_TOFIELD="MimeRichTextField"

CONVERT_FIELD = "Body"

'** do you want to save the temporary doc after you're done with it

'** (True) or delete it (False)?

SaveTempDoc = False

expdir$=GetSystemTempFolder()

If expdir$="" Then

MessageBox "You have not selected a directory", MB_OK, "Select output Directory"

Exit Sub

End If

Dim mime As NotesMIMEEntity

Dim subj As String

Set nstream=s.CreateStream

Set db = s.CurrentDatabase

s.ConvertMime = False ' Do not convert MIME to rich text|

Set dc = db.UnprocessedDocuments

Set doc = dc.GetFirstDocument

n=0

While Not(doc Is Nothing)

n=n+1

If doc.subject(0) ="" Then

subj="Spam Report"

Else

subj=validatefilename(doc.subject(0))

End If

OUTFILENAME=expdir$ & "\SpamReport.eml"

Set body = doc.GetFirstItem("Body")

fileNum% = FreeFile

fileName$ = OUTFILENAME

Open filename$ For Output As fileNum%

If body.Type = MIME_PART Then

Set mime = body.GetMimeEntity

mimebits=getmultipartmime(mime)

Print #fileNum%, mimebits

Else

Call GetRichTextAsHtmlFile(doc, CONVERT_FIELD, OUTFILENAME, True)

End If

Close fileNum%

'Kill filename$

Set doc = dc.GetNextDocument(doc)

Wend

fileName$ = "c:\program files\Test"

If isfile(fileName$) Then

result& = ShellExecute(0, "open", fileName$, TEMP, "", SW_SHOW)

Else

MsgBox CStr(n) & " emails have been exported to " & expdir$

End If

End Sub

18) Mimi, sorry, don’t know where older version Macs have their temp folder.
Andre Guirard | 6/4/2014 2:05:40 PM

But if you can find that out, use Evaluate("@Platform([SPECIFIC]) ") to find out which version of Mac the user is on, and return the correct value accordingly.

19) Please show me how to identify the MAC platform using the Evaluate?
Mimi Pham | 6/5/2014 8:18:14 AM

Dim platform

platform=Evaluate("@Platform([SPECIFIC])")

what should I add here to find out the MAC platform version?

Thanks Andre!

20) Since it sounds like you have access to the systems in question
Andre Guirard | 6/5/2014 10:20:29 AM

whereas I do not, maybe you could try it and report back what exactly it returns for different Mac OS versions.

 Add a Comment
Subject:
   
Name:
Comment:  (No HTML - Links will be converted if prefixed http://)
 
Remember Me?     Cancel

Search this blog 

Disclaimer 

    About IBM Privacy Contact