Wednesday, September 26, 2012

Fixing Another Report Designer Issue

A couple of years ago, I blogged about a bug in FRXFormatUtil.ChooseFont. I ran into another issue in that method (not really a bug) recently. If you try to select a font for an object in the Report Designer and don’t have a default printer set up (as is sometimes the case in a Terminal Services environment), you get a “printer not ready” error. It turns out the reason is the use of the “P” switch in the GETFONT() function, which tells the Font dialog to only display fonts for the current printer. That errors out if you don’t have one.

I wrapped the GETFONT() calls in TRY blocks with a retry in the CATCH without the “P” flag. Here’s the updated code:

if THIS.FontCharSet = 0
*** DH 09/26/2012: wrap calls to GETFONT() with "P" switch in TRY and
*** if it fails, retry without that switch to avoid a "printer not ready"
*** error if there's no default printer
        cFontString = getfont( ;
            THIS.FontFace, ;
            THIS.FontSize, ;
            THIS.StyleFlagsToChar( THIS.FontStyle)+"P", ;
            -1 )
        cFontString = getfont( ;
            THIS.FontFace, ;
            THIS.FontSize, ;
            THIS.StyleFlagsToChar( THIS.FontStyle), ;
            -1 )
        cFontString = getfont( ;
            THIS.FontFace, ;
            THIS.FontSize, ;
            THIS.StyleFlagsToChar( THIS.FontStyle)+"P", ;
            THIS.FontCharSet )
        cFontString = getfont( ;
            THIS.FontFace, ;
            THIS.FontSize, ;
            THIS.StyleFlagsToChar( THIS.FontStyle), ;
            THIS.FontCharSet )

(Yes, I realize this could be written more efficiently, but I wanted to make the minimum necessary changes to the code.)

Monday, September 24, 2012

More reasons to come to Southwest Fox/Xbase++ 2012






Tuesday, September 18, 2012

Creating iCalendar Files

Last year, we added something new to the Southwest Fox web site: links to iCalendar files on the Schedule page. The idea is that you can click the link for a session to add it to your calendar. It’s not meant as a replacement for Kokopelli, Dave Aring’s cool scheduling app, but so your calendar can remind you when, what, and where your next session is.

iCalendar files are pretty easy to create, as they’re just a text file with an ICS extension. Here’s the one for Steve Bondar’s HTML5 session:

DESCRIPTION;ENCODING=QUOTED-PRINTABLE:There is a lot of confusion and hype surrounding HTML5. (Rest of text removed for brevity.)

The DTSTART and DTEND elements show the start and end date and time in UTC, SUMMARY is the title, DESCRIPTION contains the session abstract, LOCATION has the room name, and UID is a unique ID.

The code to generate this simply uses text merge to insert the appropriate values into a template string. The only wrinkle is getting the date in UTC. Since Phoenix is +7 hours at the time Southwest Fox is held, I cheated and hard-coded the offset value, then used TTOC() to convert the starting and ending date/time values to the desired format:

lnTimeZoneOffset = 7 * 60 * 60
    && Phoenix is 7 hours behind GMT.
lcStart = ttoc(tdStart + lnTimeZoneOffset, 3) + 'Z'
lcEnd   = ttoc(tdEnd   + lnTimeZoneOffset, 3) + 'Z'

A more generic approach is to ask Windows for the current time zone information:

* Declare the time zone information API function and get the time zone
* information.
#define TIME_ZONE_SIZE  172
declare integer GetTimeZoneInformation in kernel32 ;
    string @lpTimeZoneInformation
lcTimeZone = replicate(chr(0), TIME_ZONE_SIZE)
lnID       = GetTimeZoneInformation(@lcTimeZone)

* Determine the standard and daylight time offset.
lnStandardOffset = ctobin(substr(lcTimeZone,   1, 4), '4RS')
lnDaylightOffset = ctobin(substr(lcTimeZone, 169, 4), '4RS')

* Determine the total offset based on whether the computer is on daylight time
* or not.
if lnID = 2  && daylight time
    lnTimeZoneOffset = (lnStandardOffset + lnDaylightOffset) * 60
else   && standard time
    lnTimeZoneOffset = lnStandardOffset * 60
endif lnID = 2

While this works just fine with Microsoft Outlook, I found a problem when trying to add a session to the calendar on my iPhone. I found this blog post by Joe Bradford that suggested the problem might be the wrong MIME type for .ICS files. Checking the IIS settings confirmed I had the same issue as Joe. The easiest way to change the settings for me (since I didn’t have access to the IIS settings directly) was to create a web.config file that changed the MIME setting and add it to the root folder of the web site:

   <remove fileExtension=".ics" />
     <mimeMap fileExtension=".ics" mimeType="text/calendar" />

However, that still didn’t resolve the problem; while I didn’t get an error, the session simply didn’t show up in the calendar. Just for grins, I tried to import an ICS file into Google Calendar; while the session did appear in the calendar, it was in the wrong timeslot (almost a year off). I manually created a calendar item and exported it, and noticed a difference in how the start and end date/times were formatted: the same as mine but without dashes or colons:


A quick change to the code generating the date/time values fixed that:

lcStart = chrtran(ttoc(tdStart + lnTimeZoneOffset, 3), '-:', '') + 'Z'
lcEnd   = chrtran(ttoc(tdEnd   + lnTimeZoneOffset, 3), '-:', '') + 'Z'

Now I can select the sessions I want (with Kokopelli’s help once it’s ready) and add them to my Outlook, iPhone, or Google Calendar so I’m reminded about each session.

Monday, September 10, 2012

Use Passive FTP

Stonefield Query has a built-in automatic update feature. It basically works like this:

  • Once every X days (where X is configurable by the user), download an XML file from our web server. The file contains the update version number, a list of files to download and install, and HTML describing the changes.
  • If the update version number is newer than the current version number, display the changes to the user and ask if they want to download it.
  • If they do, download and install the files listed in the XML file.

The last step is a little complicated. First, you obviously can’t overwrite the running EXE. Second, in Windows Vista and later, you can’t even write to the program folder without elevating privileges. We take care of that as follows:

  • Download the files to the user’s temporary files folder (which is writable).
  • Using ShellExecute with the “RunAs” parameter, launch an updater EXE and display the User Account Control (UAC) dialog requesting permission to elevate.
  • Quit so the main EXE is no longer running.
  • Have the updater EXE copy the downloaded files to the program folder, unzipping or running a setup program as necessary.
  • Have the updater EXE run the main EXE and then quit itself.

This has normally worked just fine. However, recently we had reports that Stonefield Query would take a long time to start for some users. We have great diagnostics built into Stonefield Query, so we quickly determined that the update checking, which runs at startup, was taking five minutes to execute. That sounded a lot like a timeout, which we confirmed by adding additional diagnostics to the FTP process. Another clue was that the problem only seemed to happen on Windows Server 2008 systems. Once again proving that Google is my friend, I found several possible solutions. The one that seemed most promising was to use passive mode for FTP transfers. Since I use Rick Strahl’s West Wind Client Tools, implementing this was easy: set the wwFTP object’s lPassiveFTP property to .T. Since we ourselves have a Windows Server 2008 server, it was easy to reproduce the problem and confirm that this fixed it.

Once again, Google and Rick save my bacon.