« Fun poll/discussion: The Path to Lotus Geekdom | Main| Understanding Engineers »

Quick tech post - updated DetachFile function

QuickImage   
Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


First, let me apologize for not posting the last few days. I have been extremely busy with my project, but I am nearing the end of the effort (woohoo!) I spent the last few days knocking out SPRs, and I feel really good about what we've accomplished - stay tuned for more info on that front.

Today, however, I want to share some code that I meant to post last week as my Show-n-tell Thursday post - but simply didn't have the time. I took what I mentioned in I found about attachment names in rich text items from last month (the fact that the internal name could be different than the "displayed" name, which could break code using NotesRichTextItem.getEmbeddedObject), and integrated it into my detachFile function - and here's the results.

First, here's a brief explanation of the DetachFile function to explain what it does and the parameter it takes.

Function detachFile(doc As NotesDocument, rtname As String, fname As String, fpath As String) As Boolean
   -- Purpose: Detaches a file to the specified location
   ** doc == the document containing the attachment
   ** rtname == the name of the rich text item containing the attachment
   ** fname == name of the file to detach. OPTIONAL. if blank, gets the first attachment in the rtitem
   ** fpath == where you want to place the detached file. OPTIONAL. if blank, detaches the file to a subdir (LotusGeekTEMP) under the windows TEMP dir. also, the full path to the file is returned on this parameter


Now that you understand what the detachFile function is supposed to do and what parameters it takes, let's take a look at the code itself...

The code for the new detachFile function is below. After the code I'll provide some additional comments/explanations about it.
Function detachFile(doc As NotesDocument, rtname As String, fname As String, fpath As String) As Boolean Dim rtitem As NotesRichTextItem Dim retflg As String, retval As Long Dim fullpath As String, ukey As Variant Dim obj As NotesEmbeddedObject On Error Goto errHandler detachFile = True If doc.HasEmbedded = False Then Error 1000, "No Attachments found" Set rtitem = doc.GetFirstItem(rtname) If rtitem Is Nothing Then Error 1000, "Unable to find RT item: " & rtname ' check for a name - if no name supplied, then get the first attachment If fname = "" Then Forall o In rtitem.EmbeddedObjects If o.Type = EMBED_ATTACHMENT Then Set obj = o Exit Forall End If End Forall Else Set obj = rtitem.GetEmbeddedObject(fname) ' try to get the attachment by name If obj Is Nothing Then ' there is a chance that the embeddedobject isn't stored with the supplied filename, ' so we'll check the embedded objects to make sure Forall o In rtitem.EmbeddedObjects If Lcase(o.Source) = Lcase(fname) Then Set obj = o Exit Forall End If End Forall End If End If ' we should have an attachment by now; if not, get out If obj Is Nothing Then Error 1000, "Unable to locate file" If obj.Type <> EMBED_ATTACHMENT Then Error 1000, "File is not an attachment" ' set up the detach directory If fpath = "" Then fpath = Environ("TEMP") & "\LotusGeekTEMP\" Else If Right(fpath, 1) <> "\" Then fpath = fpath & "\" End If retval = MakeSureDirectoryPathExists(fpath) ' Win32 C API call from (Declarations) fname = obj.Source fullpath = fpath & fname retflg = Dir$(fullpath, 0) ' check to see if there is a file with this name already If retflg <> "" Then ukey = Evaluate("@Unique") fullpath = fpath & "DUP-" & ukey(0) & "-" & fname End If Call obj.ExtractFile(fullpath) fpath = fullpath getOut: Exit Function errHandler: detachFile = False On Error Goto 0 Error Err, Error$ & " [in " & Lsi_info(2) & "]" Resume getOut End Function

The code begins by checking to make sure the document has at least one embedded object - if not, the code gets out.

After that it gets a handle to the specified rich text item, and then attempts to find the desired attachment. If the fname parameter is an empty string, the code gets the first attachment found in the rtitem. If a name is provided, then the code first attempts to use NotesRichTextItem.getEmbeddedObject to get it, since that is the most efficient way (if it works). If, however, it fails (maybe because of the aforementioned naming issue) it then cycles through all of the objects in NotesRichTextItem.EmbeddedObjects and checks the NotesEmbeddedObject.Source property for the desired file name. If it is found, it is returned in the obj variable.

The obj variable is then validated to make sure an object was found and that it is an attachment.

The fpath parameter is checked for a value - if fpath is an empty string the LotusGeekTEMP directory is accessed in the Windows TEMP directory. Incidentally another enhancement I included is the use of the C API call MakeSureDirectoryPathExists to make sure the LotusGeekTEMP directory is created.

Then the code checks to see if a file of that name already exists in the specified directory. If it does the attachment is detached using a unique modifier to the name to make sure the old file isn't overwritten.

Finally the full path to the newly detached file is returned on the fpath parameter.

That's it! Hope this helps you in your file detaching efforts :)

As always, comments welcome and encouraged.

Rock
**A synonym is a word you use when you can't spell the word you first thought of. --Burt Bacharach

Comments

1 - Hi

For the less tech savy, is there a new version of the detachall.nsf agent available for LN6?

Would be highly appreciated ;)

Best Regards

2 - Ahhh, some of the uninitiated - let me explain...

First, let me answer Spanky's question. Spanky, you're right on some of your observations, but let me clarify the reasoning.
** I do set detachFile = False, in case the calling code to this function doesn't have proper error trapping set up - and it just makes sense to me to set it to False, if only for readability.
** On Error goto 0 basically says "if an error is thrown from this point forward, don't handle it here - send it up to the calling code". the next line, Error Err, Error$ & " [in " & Lsi_info(2) & "]", allows me to append my own "stack trace" info to the error being thrown. LSI_INFO is a hidden lotusscript function that returns all kinds of information about the lotusscript environment and code being run; in this case LSI_INFO(2) returns the name of the current sub/function. There is a ton of info out there about LSI_INFO - Google it, or you can check out my post on error trapping, where it is mentioned (and in the subsequent discussion):

http://www.lotusgeek.com/SapphireOak/LotusGeekBlog.nsf/plinks/ROLR-5MXR69

Or Ben's posts on it:

http://www.geniisoft.com/showcase.nsf/blogsearch?SearchView&Query=%22lsi_info%22&SearchOrder=3&SearchMax=0&SearchWV=FALSE&SearchFuzzy=FALSE

** As for the "Resume getOut" I added that because a) it is a good habit to be in - when you create an error block you should have a Resume statement (or you can get NO RESUME errors), and b) for me I expect it, and I feel weird if it isn't there. And yes, technically it never gets there, since the Error call will throw it up to the calling code.

Now, in a more elaborate error handling block it can make more sense to include this stuff, even when you're throwing your own errors. For instance I have the following error block in an elaborate sub I created for my current project:
getOut: Exit Sub errHandler: Select Case Err Case 1000 On Error Goto 0 Error Err, Error$ & " [in " & Lsi_info(2) & "]" Case 1001 Msgbox Error$,64, "Notice" Case Else On Error Goto 0 Error Err, Error$ & " (err: " & Err & "; line: " & Erl & ") [in " & Lsi_info(2) & "]" End Select Resume getOut End Sub

So in this case I sometimes throw the error up to the calling code, and in some instances (when Err 1001 is thrown, which is my own error call) I want a msg box to be displayed. This way I can use my error handling to my advantage, and get really granular with it.

Hope this helps!

Rock

3 - Thanks Rock, it helps a lot. I'm gonna have to read up on LSI_Info; I'm surprised I haven't run into it before.
-Devin.

4 - Julian has a really good description on Lsi_info in his post(s) about OpenLog. You should check it out, and if you are not familiar with OpenLog, give it a look as well.

http://www.nsftools.com/blog/blog-03-2006.htm#03-13-06
http://www.nsftools.com/blog/blog-03-2006.htm#03-17-06

5 - Great stuff Rock!! Worthy of a SnTT post on its own

6 - Devin,

I've noticed that Rocky uses that little error handling routine as a standard in code he's posted, and Lsi_info(2) is always there.

Rock, is this some undocumented thing, or just a variable you always set up somewhere else?

7 - JanGB,

I'm having the same problem - but not with a profile doc, a "memo" doc going into a mail-in Db. I've got some success with dropping the rtitem and using doc.EmbeddedObjects, but it's not grabbing right.

Try that a bit. I'll work more here too.

Cheers,
Brian

8 - Does not work for me!

For days I have been struggling with detaching a file attachment, and I hoped finding this thread would be my holy grail. Alas, not so.

As with my own code, I get an error in "Forall o In rtitem.EmbeddedObjects" -- the structure is empty (during runtime) even though I can verify (manually) that the field does contain an attachment.

...I wonder if this has anything to do with the fact that the document is a profile document -- do you have any idea if that has an influence? If so it's not documented; but after all, there is some caching going on so that might screw it up.

It's for a report-generating tool that uses a half-baked report file as a template, so I really would like that template file to be centrally accessible.

Regards,
JanGB

9 - Hey there Rocky.
Nice function (kudos on keeping it clean and well commented).

I'm curious about your code after the errHandler: tag. I'm walking through it mentally (not actually running it); and I'm a bit confused.

errHandler: '<-- The only way to get here is if an error has occurred.

detachFile = False '<-- Fair enough, if an error occurs the function should indicate failure.

On Error Goto 0 '<-- Error traping within the function is now disabled. I wouldn't normally do this, but I can understand why: if an error occurs during your error handling routine, you want to avoid a perpetual loop.

Error Err, Error$ & " [in " & Lsi_info(2) & "]" '<-- HUH? You've just raised another instance of the error. At this point in the code, the error defined by Err already exists and hasn't been "cleared" by calling Resume. Are you re-raising it simply to append to Error$? If so, what is Lsi_info(2)? Forgive me, but I really don't understand what you're doing here.

Resume getOut '<-- The previous On Error Goto 0, combined with raising an error immediately prior to this line; causes this line of code to never be reached. It will therefore never run.

I'm at a loss. Could you help me understand this?
-Devin.




10 - Probably not perfect, but this is working for me. For what I'm doing, I just need to process a certain file name. I've modifed code form the "Detach all" Db in the Sandbox.
(gotta clean it up too)


Cheers,
Brian

'Dim rtitem As Variant
Dim retflg As String, retval As Long
Dim fullpath As String, ukey As Variant
Dim obj As NotesEmbeddedObject
'these are variable in Rocky's code, I'm assiging them here
'Dim rtname As String
Dim fname As String
Dim fpath As String
Dim varObjects As Variant
'rtname = "Body"
fname = "external.csv"
fpath = "\Tire Inventory\"
'On Error Goto errHandler
detachFile = True
Dim rtitem As Variant
Set rtitem = doc.GetFirstItem( "Body")
If ( rtitem.Type = RICHTEXT ) Then

res=Evaluate("@Attachments",doc)

If res(0)>=1 Then
Forall o In rtitem.EmbeddedObjects
If o.Name = "external.csv" Then
'Msgbox o.Name
Call o.ExtractFile("C:\Tire Inventory\"+o.Name)
End If
End Forall
End If
End If

11 - I apologize if this is not the right place to post this. I am a USER of Notes 6.5. I am also quite technical... but completly lost in NOTES. I am trying to use the detachFile function and am sure something is not jiving as I am constantly hitting the error Unable to find RT item: Body[in DETACHFILE] which I believe is error 1000 first one monitored.

I need some help troubleshooting this and it will be extremelly appreciated.

Thank you,
Frank Leo


12 - Sorry, what I am looking for is actually an agent for LN7!

Thanks, J

Meet Rocky

Rock - February 2010
Rocky Oliver
If you see me at a conference, please stop me and say hi!

Calendar

Search

Categories

Proudly Employed By

Wofkflow Studios

Thawte Notary

Thawte Web of Trust Notary

LOTUS GEEK gear

Social Networking


Add to Technorati Favorites

View Rocky Oliver's profile on LinkedIn

Rocky  Oliver

LotusGeek Blog Roll

Why display a blog roll when Planet Lotus does it so much better?

Dilbert

Buy my book!

Blog Buttons

Atheist - Unitarian - Humanist

Poker Players Alliance