giza: Giza White Mage (Default)
One of the neat things about the Drupal CMS is that it has a facility to schedule certain things to be run at regular intervals. For example: sending out subscription notifications or indexing posts for the search function. Those operations can take tens of seconds to complete, so you obviously don't want them executing in the middle of a page load.

The normal way to execute Drupal's crontabs is to load the script cron.php via the web interface. This is a terrible idea, since anyone with an Internet connection could cause your machine to run CPU intensive tasks at well, and bring the webserver to its knees.

An alternative way to run crontabs is with a third-party module called Poormanscron. It's a nice module, but the locking facility it has isn't so hot, and on more than one occasion I've had two cron runs execute in parallel, causing extra load on the machine. In especially bad (read: I/O bound) situations, multiple crontabs get "backed up" onto each other, and that creates huge load spikes and 60-second+ page loads until things calm down again.

There is yet a third way to run Drupal crontabs, and that's via the command line. It gives you maximum control over when and where crontabs run, and this is particularly important when you have multiple Drupal sites running. And I'm going to show you how to do it.

Step 1) Download Drush, the Drupal shell. It's a great little app for accessing your Drupal installation and performing common tasks from the command line. Install Drush (I usually put it in /usr/local/drush/, and make a symlink from /usr/local/bin/drush)

Step 2) Create a script in your home directory, and call it drupal-crontabs.sh. It should look something like this:

#!/bin/sh

#
# Our main directory for holding websites
#
ROOT=/var/www

export DRUSH_OPTIONS="-q"
#export DRUSH_OPTIONS="-v" # Debugging

#
# Errors are fatal
#
set -e

cd $ROOT

#
# All of our drupal installations under $ROOT
#
SITES="furryconnectionnorth.com"
SITES="$SITES saveardmorecoalition.org"
SITES="$SITES claws-and-paws.com"
SITES="$SITES sabanews.org"

#
# Loop through our sites to run crontabs on
#
for SITE in $SITES
do
   cd $SITE
   #
   # Disable errors, since sometimes crontabs have issues.
   #
   set +e
   drush $DRUSH_OPTIONS cron
   set -e
   cd ..
done



Be sure to season the $ROOT and $SITES variables to taste as per your specific installation.

If you run a chmod 755 on this script, you can execute it from the command line. However, you may run into file permission issues if the webserver is running as a different user than you (usually the case).

Step 3) Now you need to set up a crontab to run this script. My preferred way to do it is to create a file called /etc/cron.d/drupal-crontabs and place the following in it:

#
# Run all of our crontabs for Drupal once an hour
#
MAILTO=root
PATH=/usr/bin:/bin:/usr/local/bin

15 * * * * www-data /path/to/drupal-crontabs.sh



Make sure you are running these crontabs under the same user as your webserver.

That's it! If you want to see emailed notifications once an hour, just uncomment the line with the "-v" option for Drush. Otherwise, crontabs on all your sites will be run once an hour. Provided they do not take longer than hour to run, all of your search indexes and subscriptions will be kept up to date, and you will no longer have to worry about crontabs stomping over top of each other with Poormanscron. Enjoy!
giza: Giza White Mage (Default)
It's been a busy week or so since I posted here last.

Over the weekend, I deployed some Drupal modules on a few sites that I manage. So now the Save Ardmore Coalition site has a tag cloud, and registered users may tag existing content with custom tags. I did the same thing on the Anthrocon website, their tag cloud is over here. I also played around with the "views" module and created some pages for all-time popular posts, highest rated posts, posts with the most comments, and posts with the most votes.

I heard from an old client of mine, who asked if I could debug some ancient PHP code. Something about PHP 3 code not working right under PHP 5. :-)

I got involved with another random programming project. This one is a volunteer gig, and more of a long-term project. I have some exciting ideas for things I'd like to do, and hope they'll save us all some time and effort, as well as increase code quality.

I played around with the Simpletest module for Drupal. The version for Drupal 5 seems to be a bit round around the edges, but useful for what I have in mind, since it will be a start.

I took a look at the docs for the Yii PHP Framework. It doesn't look so bad, and the documentation is fairly detailed. I have concerns about some of the naming conventions on their classes, and the fact that a lot of the classes are invoked via static calls. (Makes unit testing and dependency injection a little difficult...), but at least there was some thought put into the way things were done. (Are you listening, PHP team?)

I am getting ready for Furry Connection North next month. Last year's convention went really well for being a first year con, and I've been impressed at how well the preparations are proceeding for this year's con. It should be fun, or at least interesting. :-)
giza: Giza White Mage (Default)
I spent about 2 hours tonight trying to figure out why my modal dialog box refused to work under Internet Explorer 6.0. The symptom was that in addition to the rest of the webpage being grayed out, the modal box was also being grayed out. Not good.

The culprit turned out to be this bit of CSS:

.jqmWindow {
   position: fixed;
}
Having the position set to "fixed" will screw you every single time. (Plus, the box displaces your page content, instead of appearing over top of it, like you would expect with a high z-index value.)

The fix is to change the position attribute from fixed to absolute. Once you do that, it behaves as expected in both MSIE 6 and FireFox.

Other than that little problem, the jqModal library is a nice little library which can create pop-up boxes within a webpage that are part of the same window. It can also "gray out" the current contents of the page and force the user to click in the box before continue. Very handy to have, if you want to make the user agree to the T&Cs of a particular website.
giza: Giza White Mage (Default)
I created an implementation of Google's MapReduce algorithm. In PHP. It was pretty crazy, but I got it to work pretty well in my test environment. It also doesn't have nearly the bells and whistles that Google's implmentation has, but I have some Evil Plans for the next revision of the framework.

I upgraded SaveArdmoreCoalition.org from Drupal 4.7 to 5.6. It wasn't nearly as painful as I thought it would be. And I was able to add some jQuery goodness in to the site now.

I transitioned another random domain to Google Apps.

I made some headway on my issues with my IRAs. But I need to wait until the office opens on Monday to get things taken care of. Gah.

I acquired some new hardware -- an extra charger for my Powerbook, so now I don't have to worry about always packing my charger whenever I travel, and an extra Linksys WRT54G router/access point. So now I can start playing with custom firmware, and not have to worry about bricking my main router.

[livejournal.com profile] unclekage and I processed just a few Anthrocon registrations earlier tonight:

Anthrocon 2008 Pre-registrations

Those of you who registered on the site recently, watch for your confirmations in the mail!
giza: Giza White Mage (Default)
- Had some friends over for Christmas on Saturday. Manage to finish off that cursed bottle of Soco, among other things.

- Had Christmas Eve dinner with the extended family. My Uncle thought it would be cute to make us sing The 12 Days of Christmas. So I sung The HotMovies version, which brought the song to an early conclusion. Also, I gave out porno to folks who were there.

- Did some jQuery UI work for one of the sites I run. Edge cases in different browsers are really starting to piss me off.

- Back in a mostly empty office for the rest of the week.

- Heading out to Chicago to visit some folks for New Year's. Turns out that I'll be staying about 5 minutes away from a colleague's office. Did I hear someone say, "Microbrew"?

- Working security for the Dorsai Irregulars at MagFest down in Alexandria, Virgina two weeks from now. If you like video games or heavy metal, it's worth checking out.
giza: Giza White Mage (Default)
Today's programmatic venting is brought to you by Internet Explorer's implementation of the setTimeout() function under Javascript.

What is setTimeout()?

setTimeout() schedules an arbitrary function call for some point in future. This is useful when you have functions that do repetitive tasks some milliseconds apartment, but not constantly.

The reason why this is used instead of a simply while (true) { ... } loop is because Javascript is a single-threaded language. So if you tie up the interpreter by executing one piece of code over and over, nothing else in the browser gets a chance to run. setTimeout() allows other pieces of Javascript (or browser events, such as clicking on a button) to run, while guaranteeing that your code will be executed at some point.

Sounds cool. How do I use setTimeout()?

Simple, like this:

setTimeout(function_name, msec);

This will cause a function called function_name to execute a certain number of milliseconds in the future.

For functions that don't need data passed into them, that works great. But what if you want to pass in parameters? In FireFox, you would do this:

setTimeout(function_name, msec, arg1);

And this works great. It will call the function called function_name again in the specified number of milliseconds. Except, MSIE doesn't like this. And rather than throw an error, it just ignores the additional arguments. So when function_name gets called again, its argument is now undefined, and choas ensues.

[Edit: It has since been pointed out to me that the most recent draft of the spec does not allow for additional parameters to setTimeout(). Okay then, but why isn't MSIE at least throwing an error? Silently accepting additional arguments and then just throwing them away is just plain stupid.]

So how do I work around this annoyance?

Closures. Up until now, I thought that closures were very geeky, and used only by CS people who were working on their PhDs. Then I saw this:

setTimeout(function() {function_name(arg1)}, msec);

Wait, what? What's going on there? Well, it might be a little easier to understand if I write it like this:

var f = function() {function_name(arg1); };
setTimeout(f, msec);


What's happening there is a variable called "f" is created, which is actually an anonymous function. That function in turn contains a single line of code: function_name(arg1); -- a call to the function we really want to call, with our specified argument. Note that all we have done so far is define a function that calls our target function. We have not executed either that function or the target function -- the target function is essentially "frozen in time". This is a very important concept, and if you've never done this before, you will have difficulty wrapping your brain around it. :-)

To get a better idea of what's going on, in this case, you could also execute the variable f as a function, since it now *is* a function for all intents and purposes:

f();

Bringing this all together, in the above example, the end result is that the function function_name will be executed after the specified interval, with the desired argument. And this will work in both MSIE and FireFox.

Just out of curiosity, what is a practical application of this function?

I'm glad you asked! As I mentioned above, Javascript is single-threaded. This means that a loop that takes a long time to run can cause the browser to appear to "freeze up" or become unresponsive, which detracts from the overall user experience. A perfect example of this would be populating a div with rows of data retrieved via AJAX. Writing 1,000 rows into that div takes about 3.2 seconds on my machine, during which the browser will not respond to commands.

What I did in my little test was instead of printing each row as it was retrieved, I instead stored them in an array. I then wrote a function which shift()ed and printed only 50 elements from that array. After 50 elements were printed, it then checked to see if there were any elements left in the array. If there were, it would call setTimeout() with itself and the array inside a closure, to be run again in 10 millseconds. The end result took a little longer, 3.5 seconds, but resulted in a much more responsive browser, and happier users.

Share and enjoy!

Source: Passing parameters to a function called with setTimeout, by Bryan Gullen
giza: Giza White Mage (Default)
Making form variables into arrays in PHP easy. One only needs to add "[]" to the name of the variable to make this happen. Example:
<input type="text" name="search[name]" id="search[name]" />
<input type="text" name="search[age]" id="search[age]" />
<input type="text" name="search[city]" id="search[city]" />
<input type="text" name="search[state]" id="search[state]" />
<input type="text" name="search[country]" id="search[country]" />
When the form is submitted and the PHP code on the processing page is run, there will be a handy little array called $search. This variable can then be passed into additional functions, looped through, etc. It's way easier than working with 5 separate variables.

Lately, I've been using jQuery in some of my web development. It lets me "do more with less", as they say. One very easy way to access specific HTML entities in a document is with pound-notation. Examples:
$("#foo").addClass("required");
$("#bar").hide();
$("#username").css("background-color", "blue");
If you just groaned, then you know what's coming. Sure enough, jQuery has issues with using square brackets in pound-notation. It seems that square brackets are used by jQuery itself as attribute selectors.

It took me some time, but I finally found a workaround, which was the real reason behind writing this journal entry -- so as to save myself and other folks time from having to find this solution again in the future. Examples:
$("[id='search[name]']").hide();
$("[id='search[address]']").show();
$("[id='search[country]']").css("color", "green");
That's about it. Happy jQuerying!
giza: Giza White Mage (Default)
Here's a little talk from the gentleman who created the jQuery javascript library. He talks about considerations in designing an API and gets into some of the stuff that jQuery went through as it evolved.



It's about an hour long, and worth a look. He has pretty good Powerpoint slides, and I just skipped through some of the talking to get to those. :-)
giza: Giza White Mage (Default)
Today's programming practice (and by "practice", I mean "anti-pattern") that pisses me off is repetition of code.

Generally speaking, having the same piece of code repeated over and over in different parts of the same system is bad, for the following reasons:

1) It makes the overall size of the system bigger. If you have a section of code that is 10 lines long, that's 9 lines longer than a single function call would be. If you have this code repeated 10 times in the system, now you're looking at 90 extra lines of code...

2) It makes fixing bugs or adding features more difficult. Instead of modifying a single function and testing it out, you now have to modify every single instance of this code, which makes for lots of repeitive (and therefore, error-prone) labor, and a larger deployment.

3) It breaks abstraction. It has been my experience that well-designed systems are full of abstractions, which in turn make the system easier to understand and maintain. For example, if I am building a page that displays an item for sale, a well designed project will have code that looks like this:

get_page_header();

get_user_info();

display_product($product_id);

get_page_footer();


While each function should still be documented, well-named functions can make it very obvious what is happening in other parts of the system that call those functions. The above example is certainly much easier to read than say, 100 lines of code that does the same thing.

4) It makes it harder to test. Having code sitting off in its own little function is easier to test, because (in the absense of globals), it is very clear what data is being passed in and what data is returned, if any. When that code is instead inlined into the middle of a page, it becomes much harder to test because now you have to worry about what comes before that section of code -- there is no easy way to isolate it. And unit tests are right out.

For further reading:
- Design pattern

- Functional design

- Anti-pattern
giza: Giza White Mage (Default)
"It works fine, except for the f*cking edge cases!"

--Co-worker M

Profile

giza: Giza White Mage (Default)
Douglas Muth

April 2012

S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags