Browsing all posts tagged how-to

My text editor of choice when I'm in Linux-land is Vim. One of its most powerful features is the ability to use regular expressions when searching. This capability makes hunting through giant log files so easy. However, I ran into a problem today that I wasn't sure how to work around.

I was looking through a large log file, trying to find the lines that included the text Internal Server Error. Making this difficult, however, were hundreds of such entries that were calling a known failing case. I wanted to weed out these known cases, and with regular expressions, I was able to. The known case I wanted to ignore looked something like the following:

[02/Feb/2023 16:06:14] ERROR [log.py:224] Internal Server Error: /api/v1/some_bad_endpoint/

This endpoint is the only one in the /api root, so I wanted to ignore /api as a part of my search. The magic is through negative look-ahead assertions, the syntax for which I was unfamiliar (\@! being the key). My search command in Vim ended up being:

/Internal Server Error: \/\(api\)\@!

Using this regular expression helped me jump to every instance of the Internal Server Error text that wasn't a call to the known-failing /api root. So handy!

AG Grid is the best JavaScript plugin I've ever worked with (I've been meaning to write about it, and I hope to soon). Oddly, one of the few features it doesn't support out of the box is clipboard support for cutting data with Ctrl + X. Copy and paste are provided, but cut support is not. Here's how I added support for this operation (we use the Enterprise variant at work, so this code is geared towards that flavor).

let gridOptions = {
    // ... other grid options ...
    'onCellKeyDown': function(e) {
        if(e.event.key == "x" && (e.event.ctrlKey || e.event.metaKey)) {
            e.api.copySelectedRangeToClipboard();
            e.api.getCellRanges().forEach(range => {
                let colIds = range.columns.map(col => col.colId);
                let startRowIndex = Math.min(
                    range.startRow.rowIndex,
                    range.endRow.rowIndex
                );
                let endRowIndex = Math.max(
                    range.startRow.rowIndex,
                    range.endRow.rowIndex
                );
                clearCells(startRowIndex, endRowIndex, colIds, e.api);
            });
        }
    },
};

let div = document.querySelector("#my-grid");
g_MyGrid = new agGrid.Grid(div, gridOptions);

The clearCells function looks like the following, and handles actually resetting the value of the cell to some default value (I set mine to 0, since I'm working with numerical data):

function clearCells(start, end, columns, gridApi) {
    let itemsToUpdate = [];
    for(let i=start; i<=end; i++) {
        let data = gridApi.rowModel.rowsToDisplay[i].data;
        columns.forEach(column => {
            data[column] = "0";
        });
        itemsToUpdate.push(data);
    }

    gridApi.applyTransaction({update: itemsToUpdate});
}

Here's the recipe for auto-authenticating in SSH with a public-private key pair. I'm recording this here because I can never remember exactly how to do this (I do it so infrequently).

Assumption: You have already created a public/private key pair to use. In Windows, this is typically done with puttygen or kittygen (or something similar).

# On the server
cd ~
mkdir .ssh
chmod 700 .ssh

# Here's the magic
ssh-keygen -i -f mykey.pub >> ~/.ssh/authorized_keys
chmod 600 authorized_keys

Once the server-side stuff is done, add the private key to the connection setup in the Kitty/Putty session configuration.

Stopping CVS Robocalls

Jan 23, 2020

In the past few weeks, I've received a couple of calls each day from CVS Pharmacy. I'm not sure what has changed on their end to cause these calls to start, but it has gotten annoying enough that I did some searching on how to disable them. Here's how to do it:

  1. Call 1-800-SHOP-CVS (1-800-746-7287)
  2. Say "More choices" (you may have to say this twice to get to the right menu; I did)
  3. Say "Calls"
  4. The system will confirm your phone number
  5. Say "Stop"
  6. Say "Correct"

How to Build a House

Jan 21, 2020

I watch far more YouTube these days than I do actual television. A series I just started is How to Build a House from the Essential Craftsman channel. In this series (that is still being released, as of this writing), they walk through the entire process of the house building process. From doing due diligence on the lot itself, to getting the building permit; from preparing the concrete forms to actually selecting the type of concrete (who knew there were different types?). I'm only about 15 or so episodes in, but it's been really eye opening in terms of the amount of work that actually goes into building a house.

Each episode teaches you something new, which has been a lot of fun. I recommend this series if you're a fan of This Old House or if you have an interest in how things are made. Here's the first video:

I stumbled on this video over the Christmas break, and I've watched it several times now. Lots of good advice in this video on being more productive, and how working both smarter and harder, is a key to success.

Do you run a WordPress site hosted at DreamHost? Are you seeing infinite redirect errors after adjusting the Do you want www in your URL? setting in the DreamHost control panel? Well friend, I had that same issue. Let me tell you how I fixed it. In this example, I will be migrating from the Add www to the Remove www value for the aforementioned setting.

Step 1: Change WordPress internal settings

  1. In WordPress, browse to the Settings » General menu item.
  2. Change both the WordPress Address and the Site Address to the new URL (in this case https://borngeek.com). Make sure there's no trailing slash.

Step 2: Change DreamHost control panel settings

  1. In the DreamHost control panel, navigate to the Domains » Manage Domains menu item.
  2. Click the Edit link next to the domain you want to change.
  3. Set the Do you want www in your URL? setting to the desired value.
  4. Click the Save button to save the change.

Step 3: Change https settings (if applicable)

This is the step that I got tripped up on (but finally stumbled upon). My site has HTTPS turned on, and there's a setting we need to change.

  1. On the Domains » Manage Domains page, click the https On link next to the domain you're changing.
  2. Change the Choose exact URL setting to the variant of your choice.

Now sit back and wait the 5 to 10 minutes for everything to take effect.

As I age, my vision is getting worse (and it's already pretty bad). At work, I use a three monitor setup: my laptop is the middle screen, and two external monitors sit to either side. Given the large screen real estate, and given my increasingly bad eyesight, I've been having a tough time finding my mouse pointer. Windows has an option to show the location of the mouse pointer when you press the Ctrl key, but that has limited usefulness (though I do use it from time to time).

I recently stumbled upon a neat feature in Windows 10 that has helped me tremendously. There are several mouse-specific features in the Ease of Access section of the Windows settings. The pointer size can be adjusted (which is helpful to a degree), but the most helpful feature is the Pointer Color setting. There's an option to adjust the pointer color based on whatever color is beneath it. It took a little getting used to, but I can now find the mouse pointer a lot easier than I could before.

Lenovo Thinkpads have an on-screen display for various hot-keys. For example, when you change the monitor brightness, or the volume level, an on-screen overlay will display showing the current brightness level or volume level, respectively. Twice, I have received laptops from Lenovo that have this software installed, but the on-screen display never appears. Frustrated by this bug, I used the Dependency Walker to troubleshoot this problem a while back, and subsequently found the solution.

Simply install the Visual Studio 2010 C++ redistributable, available from Microsoft (make sure to install the x86 version, even on a 64-bit system; the on-screen display application is a 32-bit process). Once this package is installed, and the laptop rebooted, the problem should go away.

GitLab defaults its time zone to UTC, which may not be what you want. Thankfully, you can update the value directly from your gitlab.rb file. Here's the relevant line:

gitlab_rails['time_zone'] = 'America/New_York'

Once you've added the field, simply reconfigure and restart:

sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart

A list of all the available timezones is available on Wikipedia.

The Firebug extension is a very helpful tool for web development. But did you know that you can use its console as an output target for your Firefox extensions? It's pretty simple to do:

Firebug.Console.log("Text to log"); // Output text
Firebug.Console.log(myObj); // Output an object

Is that easy or what? Having this capability is a great way to print out JavaScript objects from your Firefox extensions, making your debugging life much easier.

I have a Samsung Galaxy Tab 7.0 Plus running the Ice Cream Sandwich version (4.0.4) of Android. For some unexplained reason, the location services feature stopped working a few months ago, but only for what seemed like a few applications. Google Plus no longer knew my location, Radar Now no longer knew it, and the stock web browser was also clueless. Google Maps, on the other hand, knew right where I was. Since I use the tablet in the house, GPS isn't much help. I frustratingly was unable to fix things, until today, when I stumbled on a solution. Here's how I did it:

  1. I opened up Settings » Location services and unchecked the Location and Google search option
  2. I rebooted my device
  3. Back in Settings » Location services, I rechecked the Location and Google search option
  4. I then toggled the Use wireless networks option, and answered a prompt that appeared about using my network location in third-party apps (or something similar; I don't have the exact message in front of me).
  5. Success!

Using GPS to lock in on my position worked outside, but that alone didn't seem to set things right. Disabling the above option, rebooting, and then re-enabling it seemed to do the trick. Hopefully this will help anyone else who might have a similar problem.

I went looking for how to install iTunes recently without the bloat (because I remember seeing an article about doing just that a while back), and though I found the article, it had apparently moved from its original location. As such, I'm going to note down the steps here in case said article ever disappears. The following is intended for use on a Windows 7 64-bit system, but I think these steps should work in general. It's also intended for using an iPod classic, which is the only Apple device I care to use (though these instructions also work with the nano, mini, and shuffle variants).

  1. Download the iTunes installer
  2. Unpack the installer using something like IZArc
  3. Run the installers, using the given commands, in the following order:
    • AppleApplicationSupport.msi /passive
    • Quicktime.msi /passive (if this installer is present)
    • iTunes64.msi /passive

Adblock Plus is a terrific extension for Firefox, along with the EasyList rule set. One minor problem I've run into recently, however, is that EasyList blocks the automatic package-tracking links that appear in the sidebar in GMail (when viewing emails that contain a tracking number). I found the offending rule in the list and disabled it, allowing me to get my links back. Here's how to do it:

  1. Open the Adblock Plus Preferences dialog (Tools » Adblock Plus Preferences)
  2. Press Ctrl + F to open the find bar
  3. Search for the following text (only one rule should match it): &view=ad
  4. Disable said rule

The entire rule looks like this, in case you're curious: ||mail.google.com/mail/*&view=ad

Before we get to the meat of this article, here's a quick introductory story. The next release of Paper Plus will only allow one instance of the application to run at a time. One strange bug I ran into during the testing phase of this new feature, was the case where the application was minimized to the system tray. If a previous instance is already running (and minimized), I wanted the action of trying to start a new instance to restore the old one. For a number of reasons which I won't go into, I couldn't get the level of control I needed to restore things properly. So, to get things working, I turned to user defined messages which, happily, solved my problem. Here's a quick guide to getting custom messages up and running in a Visual C++ application.

Step 1: Define the Message ID

This is straightforward, but you'll need to make sure your definition appears in the appropriate place. I put mine in stdafx.h, which is included by nearly every file in the project.

#define WM_MYCUSTOMMESSAGE WM_USER+1

Step 2: Add the Message to a Message Map

Next, your custom message needs to be added to the appropriate message map. I added mine to the message map down in my CFrameWnd derived class. Here's how the entry looks in my case:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
...
ON_MESSAGE(WM_MYCUSTOMMESSAGE, MyCustomCallback)
...
END_MESSAGE_MAP()

Step 3: Implement the Custom Callback

Your callback function declaration must adhere to the appropriate form, as shown below:

LRESULT MyCustomCallback(WPARAM wParam, LPARAM lParam);

Custom message callbacks must always return an LRESULT, and must accept two parameters: a WPARAM and an LPARAM (down under the covers, both are simply pointers of varying types).

Once you've got the declaration in place, it's time for the definition:

LRESULT CMainFrame::MyCustomCallback(WPARAM wParam, LPARAM lParam)
{
    // Do something clever here
    return 0; // Make sure to return some value
}

Step 4: Post Your Custom Message

Now that we've got our custom message callback installed, we need to post our new message in the appropriate place. I decided to use the SendMessageTimeout function, based on some code I saw which used this function to prevent the application from hanging. Here's a variant of the code I used:

DWORD_PTR dwResult = 0;
// The hWnd parameter below is a handle to the window this message
// should be posted to. Setting this up is not shown, in order to keep
// this article as short as possible.
SendMessageTimeout(hWnd, WM_MYCUSTOMMESSAGE, 0, 0,
                   SMTO_ABORTIFHUNG, 5000, &dwResult);

And that's it! Being able to post your own messages can help you out of some sticky situations, and lets you take control of your application in some interesting new ways.

The Disk Cleanup utility that comes as a part of Windows has an annoying feature. As a part of its scan procedure, it tries to figure out how much space you'd save by "compressing old files." This step takes a ridiculously long time to complete, and is highly annoying. Thankfully, disabling this feature is simple, though it involves editing your Windows registry. As always, be very careful during the editing process.

To disable the "Compress Old Files" operation, navigate to this registry key, and delete it:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Compress old files

Once you've deleted the above key, start up the Disk Cleanup utility and marvel at how much faster it loads!

DIY Humidity Control

Jul 5, 2009

My dad has just posted some details on how he's reducing the humidity under his house. It's a pretty cool solution that utilizes 'SmartVents' (essentially a vent with some muffin fans and the appropriate sensors). Head on over to the article to get the full details.

About this time last year, I noted that our build machines at work were way out of sync in their respective local times. As a result, we were seeing a bunch of "clock skew" warnings when building our code. To fix the problem, I figured out how to use NTP on a private network. Imagine my surprise when, while performing a build today, I noticed more clock skew warnings! I checked our setup, and NTP was still functioning as expected. The problem, it turns out, was that some of our build machines had not yet changed over to Daylight Savings Time (DST), something NTP doesn't assist with. Only the oldest machines were affected, which wasn't surprising, seeing as Congress feels the need to change the DST rules every few years.

Thankfully, updating time zone information is easy to do. Here's how:

Step 1: Verify Your System Settings
Clearly, we should first check to see if we even need to update our system. To do this, we can issue this command, replacing 2009 with the appropriate year:
zdump -v /etc/localtime | grep 2009
The reported DST times should correspond with your local area. In my case, the reported date on the broken systems was April 5, not March 8. So this particular system needed updating. See the end of this article for a note on potential zdump problems on 64-bit systems.
Step 2: Obtain the Latest Time Zone Information
The latest time zone data can be obtained via the tz FTP distribution website. You'll want to get the tzdata{year}{version}.tar.gz file. In my case, the filename was tzdata2009c.tar.gz. Copy this file to the system to be updated, and unpack it in a temporary location (I put it in a subfolder in /tmp).
Step 3: Compile the New Time Zone Data
We now need to compile the new time zone data. This can be done through use of the handy zic command:
zic -d {temp_dir} {file_to_compile}
In my case, I used the name of zoneinfo for the {temp_dir} parameter, and I wanted to compile the northamerica file, seeing as that's where I live:
zic -d zoneinfo northamerica
Upon completing this compilation step, a new zoneinfo directory was created in the temporary location where I unpacked the time zone data.
Step 4: Copy the Newly Built Files
Now that the appropriate files have been built, we'll need to copy the specific region files to the right location. By default, Linux time zone information lives in the /usr/share/zoneinfo directory. Since I live in the Eastern time zone, I copied the EST and EST5EDT files to the aforementioned location (I didn't know which file I really needed, so I just grabbed both). These files will overwrite the existing versions, so you may want to back those old versions up, just to be safe. In addition to this 'global time zone' file, you'll want to copy the appropriate specific time zone data file to the right place. In my case, I copied the America/New_York file to the corresponding location in the /usr/share/zoneinfo directory. Again, you'll be overwriting an existing file, so make backups as necessary.
Step 5: Update the localtime Link in /etc
The file /etc/localtime should theoretically be a soft link to the appropriate specific time zone data file in the /usr/share/zoneinfo directory. On a few of the machines I had to update, this was not the case. To create the soft link, issue the standard command:
ln -s /usr/share/zoneinfo/{path_to_zone_file} /etc/localtime
Here's the command I issued for my example:
ln -s /usr/share/zoneinfo/America/New_York /etc/localtime
Step 6: Validate the Changes
Now that we have installed the new time zone information file, we can verify that the data has been updated properly, again by using the zdump command:
zdump -v /etc/localtime | grep 2009
This time, the dates shown should be correct. If you issue a date command, your time zone should also now be correct.

There is one word of warning I can provide to you. On some older 64-bit systems, the zdump command will seg-fault when you run it. This is a bug with the installed glibc package. I found this RedHat errata page covering the issue (at least, it refers to the package version that fixes this issue). Thankfully, I was able to compile and install the new time zone information without having to update glibc (I simply validated my changes by issuing a date command). It seems that only the zdump command exhibits the seg-fault on those older systems. Your mileage may vary.

In my previous article on Unicode, I discussed a little bit of background on Unicode, how to prep PHP to serve UTF-8 encoded content, and how to handle displaying Unicode characters. There's still a bit more we need to talk about, however, before we can truly claim internationalization support.

Prepping MySQL for Unicode

MySQL allows you to specify a character encoding at four different levels: server, database, table, and column. This flexibility becomes quite useful when working on a shared host (like I do at DreamHost). In my particular case, I do not have control over either the server or database setting (and both are unfortunately set to latin1). As a result, I set my desired character encoding at the table level.

To see what your current system and database settings are, issue the following SQL commands at the MySQL command prompt:

SHOW VARIABLES LIKE 'character_set_system';
SHOW VARIABLES LIKE 'character_set_database';

To see what character set a table is using, issue the following command:

SHOW CREATE TABLE myTable;

If you are fortunate enough to have control over the database-level character set, you can set it using the following command:

(CREATE | ALTER) DATABASE ... DEFAULT CHARACTER SET utf8;

The table-specific commands are similar:

(CREATE | ALTER) TABLE ... DEFAULT CHARACTER SET utf8;

Column level character encoding can be specified when creating a table or by altering the desired column:

CREATE TABLE MyTable ( column1 TEXT CHARACTER SET utf8 );
ALTER TABLE MyTable MODIFY column1 TEXT CHARACTER SET utf8;

I personally recommend setting the character encoding as high up as you have the capability to. That way, you won't have to remember to set it on any new tables or columns (or even databases).

If you have existing tables that do not use the utf8 character encoding, you can convert them with a simple command:

ALTER TABLE ... CONVERT TO CHARACTER SET utf8;

Be very careful when attempting to convert your data. The convert command assumes that the existing data is encoded as latin1. Any Unicode characters that already exist will become corrupted during the conversion process. There are some ways to get around this limitation, which may be helpful if you've already got some Unicode data stored in your database.

Communicating with MySQL

Once our tables are ready to accept Unicode data, we need to make some minor changes in the way we connect our application to the database. Essentially, we will be specifying the character encoding that our connection should use. This call needs to be made very early in the order of operations. I personally make this call immediately after creating my database connection. There are several ways we can set the character encoding, depending on the version of PHP and the programming paradigms in use. The first method involves a call to the mysql_query() function:

mysql_query("SET NAMES 'utf8'");

An alternative to this in PHP version 5.2 or later involves a call to the mysql_set_charset() function:

mysql_set_charset('utf8',$conn);

And yet another alternative, if you're using the MySQL Improved extension, comes via the set_charset() function. Here's an example from my code:

// Change the character set to UTF-8 (have to do it early)
if(! $db->set_charset("utf8"))
{
    printf("Error loading character set utf8: %s\n", $db->error);
}

Once you have specified the character encoding for your database connection, your database queries (both setting and retrieving data) will be able to handle international characters.

Accepting Unicode Input

The final hurdle in adding internationalization support to our web application is accepting unicode input from the user. This is pretty easy to do, thanks to the accept-charset attribute on the form element:

<form accept-charset="utf8" ... >

Explicitly setting the character encoding on each form that can accept extended characters from your users will solve all kinds of potential problems (see the "Form submission and i18n" link in the Resources section below for much more on this topic).

Potential Pitfalls

Since PHP (prior to version 6) considers a character just one byte long, there are some potential coding problems that you might run into in your application:

Checking String Length

Using the strlen function to check the length of a given string can cause problems with strings containing international characters. For example, a string comprising 10 characters of a double-byte alphabet would return a length of 20. This might cause problems if you are expecting the string to be no longer than 10 characters. Thankfully, there's an elegant hack that we can use to get around this:

function utf8_strlen($string) {
    return strlen(utf8_decode($string));
}

The utf8_decode function will turn anything outside of the standard ISO-8859-1 encoding into a question mark, which gets counted as a single character in the strlen function (which is exactly what we wanted). Pretty slick!

Case Conversions

Forcing a particular case for string comparisons can be problematic with international character sets. In some languages, case has no meaning. So there's not a whole lot that one can do short of creating a lookup table. One example of such a lookup table comes from the mbstring extension. The Dokuwiki project implemented this solution in their conversion to UTF-8.

Using Regular Expressions

The Perl-Compatible Regular Expression (PCRE) functions in PHP support the UTF-8 encoding, through use of the /u pattern modifier. If you are making use of regular expressions in your application, you'll definitely want to look into this modifier.

Additional Resources

In learning about how to add internationalization support to web applications, I gathered a number of excellent resources that I highly recommend bookmarking. Without further ado, here's the list I've created:

I ran across another weird and subtle bug in Visual Studio 2005. If you've got a solution with many project in it, you can set one of those projects to be the default project at startup (i.e. when you open the solution file). But this setting apparently resides in the user options file (.suo), which is something we don't keep in our code repository (since it differs for every user). So how can you set a default startup project that affects anyone working with your code? Simple: hack the solution file.

Thankfully, the solution file is just plain text. Apparently, if there's no user options file for a given solution, Visual Studio 2005 simply selects the first project it comes across in the solution file. Here's a quick example of what a solution file looks like (wrapped lines marked with »):

Microsoft Visual Studio Solution File, Format Version 9.00
# Visual Studio 2005
Project("{3853E850-5CD7-11DD-AD8B-0800200C9A66}") = "ProjectA", »
"projecta.vcproj", "{D9BA97DE-0D09-4C35-99D6-CC4C30A6279C}"
EndProject
Project("{3853E850-5CD7-11DD-AD8B-0800200C9A66}") = "ProjectB", »
"projectb.vcproj", "{E1D73B44-57D9-4202-A92A-0296E3583AC4}"
EndProject
Global
{ ... a bunch of junk goes here ... }
EndGlobal

In this case, Project A will be the default startup project. To make Project B the default, simply move its associated lines above Project A in the file, like so:

Microsoft Visual Studio Solution File, Format Version 9.00
# Visual Studio 2005
Project("{3853E850-5CD7-11DD-AD8B-0800200C9A66}") = "ProjectB", »
"projectb.vcproj", "{E1D73B44-57D9-4202-A92A-0296E3583AC4}"
EndProject
Project("{3853E850-5CD7-11DD-AD8B-0800200C9A66}") = "ProjectA", »
"projecta.vcproj", "{D9BA97DE-0D09-4C35-99D6-CC4C30A6279C}"
EndProject
Global
{ ... a bunch of junk goes here ... }
EndGlobal

Don't forget to grab the end tags of each project (and any child content that may live between them).