Code: Accessing Windows Clipboard

Several years ago, I found some code to access the Win32 functions for the Windows Clipboard. I don't remember where I found it, who wrote it or if it was VB code that I modified or already written for Lotusscript. I rewrote the the code as a class and put it in a script library called "Class.Win32.ClipBoard". The complete code is listed below. In my next blog entry I will describe how I am using this class for some very convenient functions.



Option Public

Option Declare


Declare Private Function GetClipboardData Lib "User32" (Byval wFormat As Long) As Long

Declare Private Function SetClipboardData Lib "user32" (Byval wFormat As Long, Byval hData As Long) As Long

Declare Private Function OpenClipboard Lib "User32" Alias "OpenClipboard" (Byval hwnd As Long) As Long

Declare Private Function CloseClipboard Lib "User32" Alias "CloseClipboard" () As Long

Declare Private Function GlobalLock Lib "kernel32" Alias "GlobalLock" (Byval hMem As Long) As Long

Declare Private Function GlobalUnlock Lib "kernel32" Alias "GlobalUnlock" (Byval hMem As Long) As Long

Declare Private Function GlobalAlloc Lib "kernel32" (Byval wFlags As Long, Byval dwBytes As Long) As Long

Declare Private Function GlobalFree Lib "kernel32" (Byval hMem As Long) As Long

Declare Private Function EmptyClipboard Lib "user32" () As Long

Declare Private Function lstrcpyLP2Str Lib "kernel32" Alias "lstrcpyA" (Byval lpString1 As String, _

Byval lpString2 As Long) As Long

Declare Private Function lstrlenLP Lib "kernel32" Alias "lstrlenA" (Byval lpString As Long) As Long

Declare Private Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (Byval strDest As Any, _

Byval lpSource As Any, Byval Length As Any)

Declare Private Function GetFocus Lib "User32" Alias "GetFocus" () As Long 


Private Const CF_TEXT = 1

Private Const GMEM_MOVABLE = &H2&

Private Const GMEM_DDESHARE = &H2000&


Class WindowsClipboard


Public Property Get Contents As String

    Dim hClipboard As Long

Dim LpStrl As Long

     Dim Resultl As Long

    Dim Clipboardstr As String


         If (OpenClipboard(0&) <> 0) Then

            hClipboard = GetClipboardData(CF_TEXT)

            If (hClipboard <> 0) Then

                LpStrl = GlobalLock(hClipboard)

                Clipboardstr = Space$(lstrlenLP(LpStrl))

                 Resultl = lstrcpyLP2Str(Clipboardstr, LpStrl)



                 Clipboardstr = "NULL"

              End If

            Call CloseClipboard()


            Clipboardstr = ""

        End If

        Contents = Clipboardstr

    End Property ' Ends the "Get" method for the "Contents" property


    Public Property Set Contents As String

         Dim lSize As Long

         Dim hMem As Long

         Dim pMemory As Long

        Dim temp As Variant


        lSize = Len(Contents)+1

        hMem = GlobalAlloc(GMEM_MOVABLE Or GMEM_DDESHARE, lSize)

        If hMem = 0 Or Isnull(hMem) Then Exit Property

        pMemory = GlobalLock(hMem)

        If pMemory = 0 Or Isnull(pMemory) Then 


            Exit Property

        End If

         Call MoveMemory(pMemory, Contents, lSize)

        Call GlobalUnlock(hMem)

        If (OpenClipboard(0&) <> 0) Then

            If (EmptyClipboard() <> 0) Then

                temp = SetClipboardData(CF_TEXT, hMem)

             End If

            temp = CloseClipboard()

        End If


    End Property ' Ends the "Set" method for the "Contents" property


End Class





14 Years Ago…

This Sunday it was 14 years ago I stepped onto an airplane in Stockholm, Sweden. In the morning hourse of January 1, 1998, my dad and my sister took me to the airport for my move to the US. I had married Angie back in August, and soon after that she told me she wanted to move back to the US. I applied for and got a resident visa (green card) and got a job with IDG in Boston. Initially we had planned to move to Seattle, but when I was offered the job in Boston I took that one instead.

Some 18 hours later I landed in Spokane, WA, where Angie met me. She had been staying with her mom, who lived in northern Idaho, while I got our appartment in Sweden packed up in 13 moving boxes (that is what I could afford to ship) and I got everything finished at my previous job at IDG in Sweden. My last task was to build an editorial system in Lotus Notes 4.6, that I built in 3 weeks. By the way, this system is still in use today, having survived several attempts to have it replaced with different other publishing systems.

After four days of driving cross-country from Idaho to Boston, we got an apartment and I started working as a full-time Lotus Notes developer on January 7.

Much have happened since. In 2000 we had our son Erik, in May 2002 we moved to Texas and I started working at Deep South as a Sr. Lotus Notes developer, in late summer of 2002 we moved into our first house, and then in July 2003 Angie and I separated and finally divorced aftyer 6 years of marriage.

It is interesting that I now have been working with Lotus Notes almost three times the time I was married, despite all the claims I have seen for many years that "Notes is Dead"…

As always, it will be exciting to see what the coming year brings me, both on a personal and professional level. Perhaps I will finally get the time to learn Xpages and/or Java?




Free Tool: Clean your NAB

As a follow-up to my previous tool that let you analyze the ACL of a database, I built another tool for my admin. For different reasons, we need to keep the mailbox of terminated users, sometimes for a shorter time but sometimes for long periods of time. As far as I understand it, if a traditional approach was used to remove a user from the system, the mail file would also be deleted. So the admin put the terminated user in the Deny Access group and change the ACL of the mailfile to include a manager, supervisor or replacement.

But because of this process, AdminP will not remove the terminated user from all the groups he/she is listed in. When you have hundreds of groups, many of them nested, this could be a real headache. So I was asked to build something simple that allows us to remove one or more specified users from all groups in the Domino Directory. Below is the result. Enjoy!

Update: I tweaked the code slightly, to avoid three separate calls to GetItemValue() and to make a line shorter. The modified code is in the end, where I update the deletelog list.


First I created a form with 3 fields:

Form with 3 fields

‘SaveOptions’ has a default value of “0” (to prevent the form from being saved).
‘Users’ is a Names field, getting it’s values using the addresses dialog. The field is multi-value and using New Line as separator.
‘LogResult’ is a multi-value text field, again with New Line as separator.

Finally I added a button to the action bar to remove the user(s). The Lotusscript code is listed below. It is using my class for mail notifications that I blogged about in November, to send a confirmation to the user running the agent. This is useful for example when you need to log all data changes done to a system.

Use "Class.MailNotification"

Sub Click(Source As Button)
 Dim ws As New NotesUIWorkspace
 Dim uidoc As NotesUIDocument
 Dim session As New NotesSession
 Dim nab As NotesDatabase
 Dim view As NotesView
 Dim doc As NotesDocument
 Dim members As Variant
 Dim newmembers List As String
 Dim delmembers List As String
 Dim users As Variant
 Dim userlist List As String
 Dim user As NotesName
 Dim nmcnt As Integer
 Dim newarray() As String
 Dim ret As Integer
 Dim removelog List As String
 Dim userarr As Variant
 Dim mail As NotesMail 
 Dim mailtext As String
 Dim listname as String
 Dim updated As Boolean

 ' *** Make sure the operator is sure
 ret = Msgbox("Are you sure?",4+32+256,"WARNING")
 If ret = 7 Then
  Exit Sub
 End If

 ' *** Get a list of users in field 'Users'
 Set uidoc = ws.CurrentDocument
 users = Split(uidoc.FieldGetText("Users"),Chr$(13))
 Forall u In users
  Set user = New NotesName(u)
  userlist(Fulltrim(user.Common)) = Fulltrim(user.Common)
 End Forall

 ' *** Get all groups in NAB and process them one by one
 Set nab = New NotesDatabase(session.CurrentDatabase.Server,"names.nsf")
 Set view = nab.GetView("Groups")
 Set doc = view.GetFirstDocument
 Do While Not doc Is Nothing
  Print "Processing " & doc.GetItemValue("Listname")(0)
  Erase newmembers
  Erase delmembers
  updated = False
  ' *** Get members in the group and create a list of the ones to keep
  members = doc.GetItemValue("Members")
  nmcnt = 0
  Forall m In members
   Set user = New NotesName(m)  
   If Iselement(userlist(Fulltrim(user.Common))) = False Then
    ' User is not among the ones to delete
    newmembers(Fulltrim(user.Common)) = Fulltrim(m)
    nmcnt = nmcnt + 1
    delmembers(Fulltrim(user.Common)) = Fulltrim(m)
    updated = True
   End If
  End Forall
  ' *** Build array of members to keep
  Redim newarray(nmcnt) As String
  nmcnt = 0
  Forall nm In newmembers
   newarray(nmcnt) = nm
   nmcnt = nmcnt + 1
  End Forall
  ' *** Write array of new members back to document and save it
  If updated = True Then
   Call doc.ReplaceItemValue("Members", Fulltrim(newarray))
   Call doc.Save(True,False)
   listname = doc.GetItemValue("Listname")(0)
   Print "Updating " & listname
  End If
  Forall dm In delmembers
   removelog(listname) = removelog(listname) & dm & ";"
  End Forall
  Set doc = view.GetNextDocument(doc)
 ' *** We are all done
 mailtext = ""
 Forall rl In removelog
  Call uidoc.FieldAppendText("LogResult", "Group '" & Listtag(rl) & "':" & Chr$(10))
  mailtext = mailtext & "Group '" & Listtag(rl) & "':" & Chr$(10)
  userarr = Split(Cstr(rl),";")
  Forall u In userarr
   Set user = New NotesName(u)     
   Call uidoc.FieldAppendText("LogResult", user.Common & Chr$(10))
   mailtext = mailtext & user.Common & Chr$(10)
  End Forall
 ' Call uidoc.FieldAppendText("LogResult", Chr$(10))
 End Forall
 Set mail = New NotesMail()
 mail.MailTo = session.CommonUserName
 mail.Subject = "[Notification] - Users removed from NAB"
 Call mail.AppendText(mailtext)
 mail.Principal = "IT Programs"
 Call mail.Send()
 Msgbox "Done removing specified user(s) from Domino Directory.",64,"Finished"
End Sub

Norwegian TV pwned

The news in the Norwegian TV channel NRK recently talked about testing the vision for elderly, the illustration for the test was picked from teh Internet. Obviously nobody looked closer at it before the broadcast…


I am sure most readers of this blog understand what's so funny… The news anchors did not.



Why my next phone will not be a Blackberry

I like my Blackberry. I currently have a Blackberry Bold 9700, and I am happy with many things. But it is almost 2 years old and it is time to look for a replacement. And after almost 4 years of being a Blackberry user, my next phone will probably be something else.

But why? The Bold has been praised multiple times by people like Vowe. The screen is excellent. It has a real keyboard, and most people (including me) think that is the best physical keyboard on the market. Well, there are several reasons.

I have upgraded the operating system from version 6 5 to version 7 6. However, the new version is not as good as the older one. Some functions are very nice, but at the same time, it uses more memory, and I suspect it got memory leaks left and right. I constantly run out of memory when loading webpages, and I see the clock/hourglass icon way too often. It also locks up frequently, sometimes just for a few seconds, sometimes so hard I have to pull the battery. Talking about pulling the battery, Blackberry users are used to do that in a nearly daily basis, especially if you want to clear the memory. But there is an app for that.

This brings us to another big issue with Blackberry. Apps. Or rather the lack of apps. Yes, there are a number of apps in the Blackberry App World. But if you look at different websites (everything from news to specialized services), chances are that they have an app for iPhone and one for Android. But very few places have an app for Blackberry. Next time you visit a website that offers apps, see what your options are. One app I use all the time is SocialScope, previously only available for Blackberry. But now there is an Android version available as well.

Other issues with Blackberry 7 6 is that the UI looks old. There were no big changes between version 6 5 and 7 6, the icons still looks boring. In BB5 there were a number of themes one could download, and also a tool that let you customize your screen/look (if you were a graphics designer). There are themes for BB6 as well, but my favourite themes did not work after the upgrade and have not been updated.

Then we have posts like this one by Darren Duke and this one by Vowe. The question is if RIM will still be around for the next two years, if I get a new phone from them now…

So it is a combination of all this, together with a wish to get a bigger screen and better performance that makes me consider a different smartphone platform. Personally I do not like being locked in to the Apple eco system. I don't have an iPod, but a MP3 player which can be connected as any other USB device, and my files (in many formats, like mp3, flac, wmv, avi, divx and mpg) can simply be dragged over. I want that for my phone too, to have the freedom to do what I want, use the files I already have.

So my next phone will most probably be an Android. I have been looking at (and reading about) the Google Nexus, but there are a few things that I don't like with it, most of all the lack of expansion (no DS-card support). Samsung Galaxy S II Skyrocket is interesting, as is the new LG Nitro. But I want the latest version of Android, Ice Cream Sandwich, which is still not available on very many phones. So it will be very interestingto see what the lineup looks like after New Years…

Update: I had the wrong OS numbers, it has now been corrected.






Script Library Issue

For a product that just turned 21, one would expect that all childhood issues were gone. Of course, I am (for now) stuck with a 17 year old slightly temperamental teenager… Anyway, I just ran into a strange issue, related to classes and code inherited from other templates.

Template A contains a script library with a class I wrote, called "AgentLog". It is used to log information about agents running in different databases. So obviously the script library need to reside in each database where I want to do the logging. Let’s say there are two, based on Template B and C. In an attempt to be clever, I simply copied the script library from Template A to Template B and C, and answered "yes" on the question if I wanted to inherit changes when the code in Template A is changed.

I then add some calls to the script library in template B and C, and push the changes out. Everything works great. A week or two later, I decide that I want to add some more info to the logging. I add one public string variable to the class, add a reference to it in one method called Terminate() and add a new method called AddMessage(). The new code is marked in red.

Class AgentLog
Private session As NotesSession
Private logdb As NotesDatabase
Private logdoc As NotesDocument
Private running As Integer
Private tstart As Long  ‘ Ticks at start
Private tend As Long  ‘ Ticks at end
Private tps As Long  ‘ Ticks per second (normally 1000)
Private message As String  ‘ Text to store in log entry, e.g. number of docs processed
 Public Sub AddMessage(txt As String)
  Me.message = Me.message + txt
End Sub

Public Sub Terminate()
  Dim seconds As Integer
  If logdoc Is Nothing Then
   Exit Sub
  End If
  tend = Getthreadinfo( THREAD_TICKS )
  seconds = (tend – tstart) / tps
  Call logdoc.ReplaceItemValue("EndTime", Now() )
  Call logdoc.ReplaceItemValue("Seconds", seconds )
  Call logdoc.ReplaceItemValue("Message", message )
  If running = True Then ‘ Check if not terminated gracefully
   Call logdoc.ReplaceItemValue("Terminated", "Yes" )   
  End If
  Call logdoc.Save(True,True)
End Sub

Nothing special, right? I did not even modify New() at all. All this was done in anticipation of adding the additional logging later in Application B and C (based on the templates B and C). The updated Template A was put in production, and the next night the update process refreshed the design of Template B and C with the new code, and they in turn updated Application B and C.

Now the users suddenly got an error message when code using the script library was launched: "Type mismatch on external name: AGENTLOG". I tested it and got the same error. I decided to turn on the debugger to see where exactly it happened. And of course then it worked! Debugger turned off, error again. I recompiled all Lotusscript in the application, and it worked with no debugger on. I then recompiled all code in Template A, refreshed design of Template B and C and recompiled all code in Template B and C. Despite this, the next day we had the same problem…

Finally I ended up not linking the script library from Template A to Template B and C. I will just have to copy it over again every time I make a change to every template where I use it. Anyone got any ideas what the problem might be?.



Re: Using Lotusscript lists to increase performance

In response to: Using Lotusscript lists to increase performance

I now modified the code to use NotesViewEntry.ColumnValues() to get the value I am looking for. I also had to add a column displaying that field in my view.
The code actually took longer, 370 seconds, but all that was in the calls to doc.Save(). The actual lookup went from a total of 15 seconds down to just below 5 seconds.

Using NotesViewEntry.ColumnValues()

So let’s remove the time-consuming doc.Save() command, just to see what the actual execution time is. Here is the result for my four versions of the same import agent:

Agent Time
 Individual lookups for each entry imported  370 sec
 Building list of documents using view lookup/doc.GetItemValue() before importing  55 sec
 Building list of documents at first use, using view lookup/doc.GetItemValue()  54 sec
 Building list of documents at first use, using viewentry lookup/entry.ColumnValues()  36 sec

By removing the print statement that give feedback about the progress (printing every 100 entries), additional 3 seconds would be saved.

So there is a substantial performance benefit when you don’t have to open the actual document and read a value, but can use a view column value instead. If you need multiple values from one document, one easy way is to combine all values into one column, separated with a character that you will never enjounter in the data (I am partial to the ~ character, it also makes it easy to read) and then use Split() to get the individual values.




Trend Micro: Google is most insecure

According to Trend Micro's Third Quarter Threat Report, Google had 82 vulnerabilities during the third quarter 2011. Oracle had 63, followed by Microsoft with 58, Apple with 49, Adobe with 42 and Mozilla and IBM both with 39 during the same period.

In other news: University of Dayton and General Motors are moving from Lotus Notes to Google Mail, while the CIO at the City of Los Angeles demands that Groupwise "be hauled out of its coffin, given a quick slap and defibrillation, and pressed back into service" to replace Google Mail and Apps. Link to letter here, another article here.

According to Consumer Watchdog, the rollout [in Los Angeles] has hit snags, with the police deciding that security on the vaunted platform isn’t adequate for users in the City Attorney Criminal Branch, fire department arson investigations, public safety users, parking police, street services investigations, parks rangers, and “any other City entities that access criminal history data”.





The DRM graveyard: A brief history of digital rights management in music

Very interesting.

There are more than a few reasons digital rights management (DRM) has been largely unsuccessful. But the easiest way to explain to a consumer why DRM doesn't work is to put it in terms he understands: "What happens to the music you paid for if that company changes its mind?" It was one thing when it was a theoretical question. Now it's a historical one. Rhapsody just had the next in a line of DRM music services to go–this week the company told its users than anyone with RAX files has unil November 7 to back them up in another format or lose them the next time they upgrade their systems.

Full story here.  As Vowe says: DRM is bad for the customer.




LS12 Session Proposal – Error 500 when submitting

I just tried to submit a session for Lotusphere 2012. I filled out the form, and pressed the "next" button as instructed.

This is the result:

Error 500

I know a few good Notes/Domino developers out there that could create a working form quickly… 🙂


Update: I called the support number on the page and was asked to mail a screenshot. I did and they said it worked when they tested, but it still did not work for me. There is documentation about the error here. The documentation indicates that it has to do with data being too long, or wrong data type. All fields on the form are below the posted limits (close, but at or below). 997 characters for the main abstract, 350, 347 and 328 characters for the three bullet points and 92 characters for the title.
Hint to the developer of the form: best practices is to do validation before submission, especially if the server will blow up if anything is invalid…


Update 2: I was finally able to submit it this afternoon. Not sure if it was because I used Firefox (unlikely, since it seemed to be a server issue) or if IBM fixed it.



Re: Lotusscript: Mail Notification Class – followup

In response to: Lotusscript: Mail Notification Class

I also want to bring attention to how I am using lists in the class. The different sets of recipients (To, CC and BCC) are all stored in lists. The name/email is both the list tag and the value, e.g. p_sendto(“”) = “”.

There are several benefits of using lists. First of all, it is very easy to add items to a list. When using an array you need to redim it (using ReDim Preserve in order not to lose existing data) before adding items.
Secondly, with a list it is trivial to remove one specific item, and you can easily loop though the list and build an array to pass into the fields on the mail document later. It is not nearly as easy to remove items from an array, even if you can clear out an entry and use FullTrim() to remove any empty items.

This is just one more example of how powerful lists are.



Lotusscript: Mail Notification Class

Back in August 2009, I posted a small class I had created to make it easier for me to create mail notifications in my Lotusscript agents. Since then I have improved on it, and I wanted to post the latest version. One recent feature I added is to send external mail to the internet that looks like it is coming from a different user. This is often needed when you have an agent signed with a developer/admin ID, but you want the mail to look like it is coming from a particular department or user.
This is done by using the (unsupported) method of saving the document in instead of using doc.Send() to mail it directly. What I found is that you should use on the same server as the agent is running on, you can not access on a different server. The code reflect this.

Other new features are support for doclinks, attachments and an option to display a warning message at the end that the mail comes from an un-monitored role account. This warning is actually turn on by default.

Here is an example of how to use the class:

Option Public
Option Declare
Use "Class.MailNotification"

Sub Initialize
 Dim mail As NotesMail
 ' *** Create a mail
 Set mail = New NotesMail()
 ' Set receipient and subject
 mail.MailTo = ""
 Call mail.AddMailTo("")
 mail.Subject = "Please Read This"
 mail.Principal = ""
 ' Create body
 Call mail.AppendText("Testing email...")
 Call mail.AppendNewLine(2)
 Call mail.Send()
End Sub


And here is the class itself. Enjoy!

Option Public
Option Declare

Class NotesMail
 Public maildoc As NotesDocument
 Public body As NotesRichTextItem
 Private p_subject As String
 Private p_sendto List As String
 Private p_copyto List As String
 Private p_blindcopyto List As String
 Private p_principal As String
 Public NoReply As Integer
 Public mailbox As NotesDatabase

 Public Sub New()
  Dim session As New NotesSession
  Dim mailservername As String
  mailservername = session.Currentdatabase.Server
  ' Using directly is unsupported, but is the
  ' only way to make the mail look like it is actually
  ' sent from another address, in our case the principal.
  Set mailbox = New NotesDatabase(mailservername,"")
  If mailbox.Isopen = False Then
   Print " on " & mailservername & " could not be opened"
   Exit Sub
  End If
  Set me.maildoc = New NotesDocument(mailbox)
  Call me.maildoc.ReplaceItemValue("Form","Memo")
  Set me.body = New NotesRichTextItem(maildoc,"Body")
  me.p_subject = ""
  me.p_principal = ""
  ' Empty lists for addresses
  Erase me.p_sendto
  Erase me.p_copyto
  Erase me.p_blindcopyto
  me.NoReply = False ' Default is to add a disclaimer to the end
 End Sub

 Public Property Set Subject As String
  me.p_subject = FullTrim(Subject)
 End Property

 Public Property Get Subject As String
  Subject = me.p_subject
 End Property

 Public Property Set Principal As String
  me.p_principal = FullTrim(Principal)
 End Property

 Public Property Get Principal As String
  Principal = me.p_principal
 End Property

 '*** Recipient address (mailto) functions ***

 Public Property Set MailTo As String
  me.p_sendto(FullTrim(MailTo)) = FullTrim(MailTo)
 End Property

 Public Property Get MailTo As String ' Get the first address only
  ForAll mto In me.p_sendto
   MailTo = mto 
   Exit ForAll
  End ForAll
 End Property

 Public Property Set SendTo As String ' Alias for MailTo
  MailTo = SendTo
 End Property

 Public Property Get SendTo As String ' Alias for MailTo
  SendTo = MailTo 
 End Property

 Public Sub AddMailTo(address As String) ' Additional address
  me.p_sendto(address) = address
 End Sub

 Public Sub RemoveMailTo(address As String) ' Remove address
  Erase me.p_sendto(address)
 End Sub

 ' *** Functions for CC address ***

 Public Property Set MailCC As String
  me.p_copyto(FullTrim(MailCC)) = FullTrim(MailCC)
 End Property

 Public Property Get MailCC As String ' Get the first address only
  ForAll mcc In me.p_copyto
   MailCC = mcc 
   Exit ForAll
  End ForAll
 End Property

 Public Sub AddMailCC(address As String)
  me.p_copyto(address) = address
 End Sub

 Public Sub RemoveMailCC(address As String)
  Erase me.p_copyto(address)
 End Sub

 ' *** Functions for BCC address ***

 Public Sub AddMailBCC(address As String)
  me.p_blindcopyto(address) = address
 End Sub

 Public Property Set MailBCC As String
  me.p_blindcopyto(FullTrim(MailBCC)) = FullTrim(MailBCC)
 End Property

 Public Property Get MailBCC As String ' Get the first address only
  ForAll bcc In me.p_blindcopyto
   MailBCC = bcc 
   Exit ForAll
  End ForAll
 End Property

 Public Sub RemoveMailBCC(address As String)
  Erase me.p_blindcopyto(address)
 End Sub

 ' *** Functions for email body ***

 Public Sub AppendText(bodytext As String)
  Call me.body.AppendText(bodytext) 
 End Sub

 Public Sub AppendDocLink(doc As NotesDocument, comment As String, linktext As String)
  If FullTrim(linktext) = "" Then
   Call me.body.AppendDocLink(doc, comment) 
   Call me.body.AppendDocLink(doc, comment, linktext) 
  End If
 End Sub

 Public Sub AppendNewLine(cnt As Integer)
  Call me.body.AddNewline(cnt) 
 End Sub

 Public Sub AddNewLine(cnt As Integer)
  Call me.body.AddNewline(cnt) 
 End Sub

 Public Sub AttachFile(filename As String)
  Call me.body.EmbedObject(1454,"",filename) 
 End Sub

 ' *** Send the mail
 Public Sub Send()
  Dim session As New NotesSession
  Dim richStyle As NotesRichTextStyle
  If me.subject<>"" Then
   maildoc.Subject = me.subject
  End If
  If ListItemCount(me.p_sendto)>0 Then
   maildoc.SendTo = ListToArray(me.p_sendto)
   maildoc.Recipients = ListToArray(me.p_sendto)
  End If
  If ListItemCount(me.p_copyto)>0 Then
   maildoc.CopyTo = ListToArray(me.p_copyto)
  End If
  If ListItemCount(me.p_blindcopyto)>0 Then
   maildoc.BlindCopyTo = ListToArray(me.p_blindcopyto)
  End If
  If me.p_principal<>"" Then
   Call maildoc.ReplaceItemValue("Principal", me.p_principal)
   ' If principal is set, we want to fix so mail looks like
   ' it is coming from that address, need to set these fields
   Call maildoc.ReplaceItemValue("From", me.p_principal)
   Call maildoc.ReplaceItemValue("Sender", me.p_principal)
   Call maildoc.ReplaceItemValue("ReplyTo", me.p_principal)
   Call maildoc.ReplaceItemValue("SMTPOriginator", me.p_principal)
  End If
  ' If NoReply is set (default), append red warning...
  If NoReply = True Then
   Set richStyle = session.CreateRichTextStyle
   richStyle.NotesFont = 4
   richStyle.NotesColor = 2
   richStyle.Bold = True 
   Call me.body.AppendStyle(richStyle)
   Call me.body.AddNewLine(1) 
   Call me.body.AppendText("*** DO NOT REPLY TO THE SENDER OF THIS MESSAGE! ***") 
   Call me.body.AddNewLine(1) 
   Call me.body.AppendText("*** IT IS AN AUTOMATED SYSTEM MAIL ***") 
  End If
  Call maildoc.ReplaceItemValue("PostedDate",Now())
  If me.p_principal<>"" Then
   Call maildoc.Save(True,False) ' Save in
   Call maildoc.Send(True)   ' Send mail normally
  End If
 End Sub

 ' *** Private functions called from within the class ***

 ' *** Convert list to array
 Private Function ListToArray(textlist As Variant) As Variant
  Dim i As Integer
  Dim temparray() As String
  ReDim temparray(0) As String
  ForAll t In textlist
   temparray(UBound(temparray)) = t
   ReDim Preserve temparray(UBound(temparray)+1) As String
  End ForAll
  ListToArray = FullTrim(temparray)
 End Function

 ' *** Count items in a list
 Private Function ListItemCount(textlist As Variant) As Integer
  Dim cnt As Integer
  cnt = 0
  ForAll t In textlist
   cnt = cnt + 1
  End ForAll
  ListItemCount = cnt
 End Function

End Class


Update: Please don’t use the email addresses in the example code above to actually send email…


Tip of the Week: Open a Hidden View in Notes

Earlier today I talked to someone who needed to delete a large number of ToDo entries in his mail file. I suggested to use either Ytria scanEZ or Martin-Scott NoteMan. As he did not have those tools installed on the machine he was working op, he thought about creating a hidden view and simple delete the documents from there.

When he opened up the design, he found a hidden view that would work, but he did not remember how to open it in the Notes client. I use this all the time, as I have several views I use for support or debug purposes, so I could tell him how to do it. I wanted to share that with everyone else, in case someone else need to do this and don't remember.

imageOpen the database and go to any view. This is important, if you are not in a view, you don't have the correct menu items. Then hold down Ctrl-Shift and select "View" and "Go To…". This will open a dialog box that will display all views, including the hidden ones (in parenthesis). Now you can release the Ctrl-Shift keys and select the view.

That's it.

By the way, I highly recommend the tools mentioned above.
NoteMan is just $395 for the whole suite. Any developer not using it should go get it. I blogged about the tool back in October 2010.
Ytria scanEZ is more, they don't post the price on their site but if I remember correctly it is something like $595. I use several of their tools, and they are really helping me keep a consistent look-and-feel to my applications. I mentioned the tools back in April 2010.
Both companies are usually represented at Lotusphere, so make sure you drop by their booths and get a demo.




Using Lotusscript lists to increase performance

Earlier this week I had to write an agent to read a CSV file and create Notes documents based on the content. Nothing difficult, this is something most of us do all the time. However, each entry in the CSV file had a producer code. I needed to do a lookup into the database and get the matching producer (also called agency) document and read a particular field from it. This field had to be stored in the new document I was creating, to tie the new entries to an existing producer/agency.

There were 3686 producers in the database, and 47731 entries in the CSV file. In other words, there were several entries per producer.

One solution is to load all agency documents into a list, then perform the lookup against the list using IsElement(). Since the number of producers are much fewer than the number of entries in the CSV file, I figured that I would save some substantial time vs. performing separate lookups for each entry.

I wrote three different version of the importer, as identical as possible.
The first version performed a view lookup for each entry in the CSV file to get the producer. It finished in 520 seconds.
The second version used a NotesViewEntryCollection to loop through all producers and load them into a list, then processed the CSV and used the list to get the producer documents. This one finished in 257 seconds.
The third version is using a combination. I do not pre-load the list with producer documents, instead I use IsElement() on each entry in the CSV file to check if the list already contains an element for the producer. If not, I perform the lookup and store the document into the list, to use next time the same producer is needed. This agent finished in 260 seconds.

The third version is the approach I have been using in the past. The benefit with that approach is that I only load the producers where I actually have data. Suppose only 1000 different producer codes are listed in the CSV file, I will just perform 1000 lookups, instead of 3686.

I use TeamStudio Profiler to time and profile my code, and I noticed something interesting. When the document was loaded into the list in program two and three, the time it took to read the value and store it into the new document was much shorter than when I had just performed the lookup, even if the code was identical. You can see it in the screenshots below. I hope André Guirard or someone else that knows more about the internals of Lotusscript can explain this. See update at the end of this article!


Version one – individual lookups for each entry (520 seconds to execute):
Individual Lookups



Version two – Preloading all producers before import (257 seconds to execute):

Pre-load into list


Version three – Lookup once, store in list for later use (260 seconds to execute):

Lookup once then store in list


Update: I just talked to André Guirard   about this. I isolated the slow code to doc.GetItemValue(), and André explained that the slowness I see is because of how Notes documents are handled. The note is not "cracked open" until an item is needed. So when I store the note in the list (example 2 and 3), it is opened once (which is slow), and then the following times I access the same document it is very fast, as it already is open. In the code with individual lookups (example 1), the document has to be "cracked open" over and over again, as the object is recreated for every entry.




Sony buying out Ericsson

Sony is buying out the Swedish telecom company Ericsson's share in Sony Ericsson, the mobile phone company they created back in 2001. Sony is paying Ericsson 1.05 billion Euros in cash for their 50% share of the company. Reports also say that Sony is planning to change the name of Sony Ericsson in the future.

Press release here.





Ernst & Young moving 150,000 users from Notes to Exchange

Update: As the article (swedish only) now has been published, I am re-publishing this blog entry.

At least that is what is indicated if you look at Neil Langston's profile at LinkedIn…

Supposedly it is a mail-only migration (according to my source), they will keep their Notes/Domino applications. So the end result is just the purchase of new servers and additional licenses being paid to Microsoft. I am sure Microsoft will spin this as a win for them and a loss for IBM, but I don't see IBM losing any money. If Ernst & Young is paying maintenance today, they will most probably continue to do that for a while, thus IBM will see pretty much the same revenue stream (minus a few mail-only servers, perhaps). If Ernst & Young are not on maintenance, IBM is already not getting any revenue, so no different there either.

Neil Langston LinkedIn profile



Another Pioneer Lost: Dennis Ritchie 1941-2011

This morning I woke up to the news that Dennis Ritchie died a couple of days ago. Some of my readers may not know who he was, he was never a showman like Steve Jobs, or took part in product launches. None the less, he was one of the big names and early pioneers in IT.

Ritchie was best known as the creator of the C programming language and a key developer of the Unix operating system, and as co-author of the definitive book on C, The C Programming Language, commonly referred to as K&R (in reference to the authors Kernighan and Ritchie).

Ritchie's invention of C and his role in the development of UNIX alongside Ken Thompson has placed him as an important pioneer of modern computing. The C language is still widely used today in application and operating system development, and its influence is seen in most modern programming languages. UNIX has also been influential, establishing concepts and principles that are now precepts of computing.

Source: Wikipedia

I remember when I started learning C, back in 1989. I had already been exposed to first Basic and then Pascal earlier, and until I understand the beauty of C, the whole thing with pointers confused me to no end. But eventually I got pretty decent at writing C code. I also started using Unix (HP-UX) about the same time. A couple of years later, I wrote a couple of small Windows program, again using C. Back in the early days of Windows, C was the only language you could use to write Windows applications.

It is amazing that a person who created some of the most widely used products is not more well known. Dennis Ritchie is at least as important in the history of computing as Steve Jobs. C and Unix may not be as sexy as iPhones, iPods and Macintosh computers, but they are probably more important if you look at the big picture. Apples OS X is based on Unix, and developers use Objective C to create programs and apps for OS X and iOS. Without Dennis Ritchie, the world would probably look very different.


Dennis Richie 1941-2011

 Dennis Ritchie
1941 – 2011



Samsung Nexus Prime – Verizon exclusive?

Additional details about the upcoming release from Samsung of the Nexus prime, the first phone with Android 4.0 (Ice Cream Sandwich) have been published on some websites.

The new phone, which is expected be be unveiled next week, has a 4.65" display with 1280×720 resolution, a 5MP camera, 1 GB of system RAM and 32 GB of built-in user memory. The dual-core processor is running 1.2 GHz or 1.5 GHz (according to what source one listen to). Reports claims that it is an LTE (Long Term Evolution) phone, and that it will be available only through Verizon, at least initially.

The Nexus Prime will probably be called Droid Prime when it is sold by Verizon. AT&T is also working on it's LTE network, but the two carriers use different frquencies, so they are not compatible with each other. The question is how the Nexus Prime will work overseas, where LTE networks are also being built right now, using different frequencies. Some reports say that Nexus Prime will support HSPA or HSPA+, but what about GSM/3G support, for fallback internationally? And will there be quad-band GSM support? Hopefully those questions will be answered next week.

Samsung Nexus Prime




End of content

No more pages to load