Thursday, December 11, 2008

Camera Focal Range Data application

I'm currently using a point & shoot camera (Canon S2 IS), but have a DSLR on the way (Canon Rebel XS). The DSLR comes with a kit lens that ranges from 18-55mm (28.8mm - 88mm in 35mm equivalents), which is quite a bit shorter than my p&s (up to 432mm equiv!). So I got worried that I wouldn't be too happy with just the kit lens. At the same time, I wanted to try out LINQ in .NET, so a quick project was born...

I wrote a bare-bones console application that reads in EXIF data from images, extracts the focal length of each, and uses some simple LINQ queries that lets me see exactly how many photos were taken in wide, normal, tele, and super tele modes. It recurses through directories from a starting directory you specify, and tries to get info on all JPEG images.



Right now, there's no nice GUI, it's not multi-threaded, you can only search one folder at a time, there is no progress bar, and you can't actually see what images fall into which category, but if there's any interest in this app at all, I'll add all that sometime soon.

Also, you have to enter a 35mm focal length equivalent for the categories to be accurate. This is annoying, because for point and shoots, that info is sometimes hard to find. I had to actually take a picture all the way zoomed in, and compare the specs on dpreview.com with the EXIF data (72mm max zoom in the EXIF = 432mm max zoom in the specs, so it has a multiplier of "6"). I may end up looking up the multipliers on a bunch of popular cameras and just ask the user to select his/her camera, or optionally manually enter the focal length multiplier if the user's camera is not on the list.

Anyway, the application is fully working in it's current state - all you need is the .NET 3.5 framework. If you want to use the app and can't figure out the proper multiplier to use, just post a comment here, and I or another reader can certainly help you out.

No crazy code in this app, but here's the LINQ part:

//generic list
private List<double> zoomData;
...
zoomData = new List<double>();
...
//code in the directory/file loop
zoomData.Add(focalLength * focalLengthX);
...
int shortTele = (from t in zoomData where t > 100 && t <= 150 select t).Count();

In my case, I was happy to see that I had a significant amount more wide and normal shots than telephoto shots, so maybe I won't need an extra lens after all.

You can download the app here.

UPDATE: Picasa somehow figures out the 35mm equiv. from the EXIF data. I'm going to figure out how they're doing it and update my application soon.

Wednesday, December 3, 2008

System.Net.Mail throws "no such user" exceptions w/ Lotus Domino!

Code that I had running for years without issues suddenly broke with the error "Mailbox unavailable. The server response was: user@domain.com... No such user". Very useful, as with this information, I can now automate (or at least partially automate) internal mailing list management.

For now, I've just added code to catch this specific exception (in addition to the catch-all SmtpException) to have it generate a work order ticket if there's a problem:

try {
smtp.Send(newsletter);
} catch(System.Net.Mail.SmtpFailedRecipientException ex) {
//generate work order...
}

There's also "SmtpFailedRecipientsException" which triggers only if it can't send an email to all the recipients, though I don't see why you would use this over SmtpFailedRecipientException, as the latter informs you in the exception message if all recipients haven't received the email in addition to handling the case where one or some recipients failed.

However, I've noticed that if more than one recipient email fails, it only displays the first email address that failed in the error message. Makes sense, since it probably is processing one email completely at a time, and if it fails on one, it doesn't go any further. The solution to this, I guess, would be to have each email sent out within a loop of separate SMTP calls instead of using the handy "mail.To.Add" method. There would most likely be a performance hit doing it this way, though.

I'm going to keep looking into it, but does anyone know of a better way?

Also, I assume this works with MS Exchange as well, but don't know anyone using email with Exchange to test with. I have verified it doesn't work with Gmail and Hotmail, so this is best used only with an environment where you know that everyone is on the same mail server.

Friday, September 19, 2008

Java SwingX ActiveDirectory/LDAP LoginService

Sorry for the long break from posting, but I haven't worked on anything interesting enough to post for a while. I've just completed some basic ActiveDirectory auth code with a Java Swing application. I had a heck of a time connecting to AD at all with Java - all the examples I found online seemed like they should have worked, but my test code just hung with no errors, and with no useful debugging info. I'm still not sure what was going on exactly, but the following helper class worked for me (the most notable exception between this code and my previous attempts is that it's now specifying the domain controller):

LDAPUtils.java:
package app;

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

/*
@author Randy Coates
Modified slightly by Dave Gruska
*/
public class LDAPUtils {
static String ATTRIBUTE_FOR_USER = "sAMAccountName";
public Attributes authenticateUser(String username, String password, String _domain, String host, String dn) {
String returnedAtts[] ={ "sAMAccountName", "memberOf" };
String searchFilter = "(&(objectClass=user)(" + ATTRIBUTE_FOR_USER + "=" + username + "))";

//Create the search controls
SearchControls searchCtls = new SearchControls();
searchCtls.setReturningAttributes(returnedAtts);

//Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
Hashtable environment = new Hashtable();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

//Using starndard Port, check your instalation
environment.put(Context.PROVIDER_URL, "ldap://" + host + ":389");
environment.put(Context.SECURITY_AUTHENTICATION, "simple");

environment.put(Context.SECURITY_PRINCIPAL, username + "@" + _domain);
environment.put(Context.SECURITY_CREDENTIALS, password);
LdapContext ctxGC = null;

try {
ctxGC = new InitialLdapContext(environment, null);

//search for objects in the GC using the filter
String searchBase = dn;
NamingEnumeration answer = ctxGC.search(searchBase, searchFilter, searchCtls);
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult)answer.next();
Attributes attrs = sr.getAttributes();
if (attrs != null)
return attrs;
}
}
catch (NamingException e) {
e.printStackTrace();
}
return null;
}
}
I'm then extending SwingX's LoginService to work with this class and return some extra details, like the user's group (this can be easily expanded to capture an Active Directory attribute):

LDAPLoginService:
package app;

import javax.naming.directory.Attributes;
import org.jdesktop.swingx.auth.LoginService;

/*
@author Dave Gruska
*/
public class LDAPLoginService extends LoginService {
private String domain;
private String host;
private String dn;
private String userName;
private String groupName;

public String getDomain() {
return domain;
}

public void setDomain(String domain) {
this.domain = domain;
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public String getDn() {
return dn;
}

public void setDn(String dn) {
this.dn = dn;
}

public String getUserName() {
return userName;
}

public String getGroupName() {
return groupName;
}

public LDAPLoginService(String domain, String host, String dn) {
this.domain = domain;
this.host = host;
this.dn = dn;
}

@Override
public boolean authenticate(String name, char[] password, String server) throws Exception {
LDAPUtils LDAPlogin = new LDAPUtils();

//TODO: investigate if there's a more efficient way to convert this
StringBuilder passwd = new StringBuilder();
for(char c : password) {
passwd.append(c);
}

Attributes attrs = LDAPlogin.authenticateUser(name, passwd.toString(), this.domain, this.host, this.dn);

if(attrs == null) {
//login failed
return false;
} else {
//login successful
String[] splitUserName = attrs.get("sAMAccountName").toString().split(":");
userName = splitUserName[1].trim();
groupName = attrs.get("memberOf").contains("CN=AppAdmins,CN=Users,DC=domain,DC=com") ? "admin" : "user";

return true;
}
}
}
and finally, the presentation layer code displays a login pane (JXLoginPane dialog) that gets called in the constructor right after the components are initialized:
loginService = new LDAPLoginService("domain.com", "[domain controller IP]", "cn=Users,dc=domain,dc=com");
JXLoginPane.Status status = JXLoginPane.showLoginDialog(null, loginService);

if(!status.equals(status.SUCCEEDED)) {
System.exit(0);
}

loggedInAsLabel.setText(String.format("%S (%s)", loginService.getUserName(), loginService.getGroupName()));
BTW, the System.exit code only gets called when the user gives up on trying to log in.

Monday, August 11, 2008

My personal Photoshop use is decreasing drastically

When I got Photoshop 4 back in the day, I used it daily to do work at home and to do my own personal creations. I also scanned in lots of photos and fixed them up.

Now that I'm a full-time developer (as opposed to part designer/part developer), I find myself using PS less and less. I've since upgraded to CS2, which in retrospect was actually quite a waste of money - PS 6 had everything I needed. My first digital camera was great (Sony DSC F-707), but almost every image really required post-processing to really look good, so I used PS quite a bit back then. I now have a Canon S2 IS, which has excellent color reproduction - good enough that I don't bother with most images anymore. And I've pretty much stopped doing my own image composites for fun (though I occasionally knock a few off for friends/co-workers).

But I still do a fair amount of image manipulation overall, considering I take tons of photos. I've found Google's Picasa software handles most of my modifications quite well (red-eye, cropping, slight color fixes) - its "Fill Light" tool is awesome for fixing underlit subjects btw, and way quicker than any method I've ever used in PS. Picasa also manages the photos better than Adobe's Bridge or Lightroom, IMHO. And I can upload straight to Snapfish. So my workflow all pretty much happens in one place.

For situations like noise reduction, I use Neat Image (free version). You can't beat its simplicity, and the results are fantastic in my experience so far (I've mostly used it to clean up high ISO noise in otherwise unacceptable-quality pictures). For stitched panoramas I use Hugin (also free). And for quick fixes, I use the free (and open source) Paint.NET application, which loads much quicker than PS, and has many of its basic tools.

There's still situations where I still use Photoshop - mostly for true HDR and HDR-like processing (levels adjustment layers, dodge/burn) to bring out detail, and I recently created a family photo for my parents from a number of different pictures that I wouldn't even consider using another application for, but it's probably dropped to about 5% or so of my total image processing time these days.

Monday, July 28, 2008

Retreiving and sending Hotmail email from Gmail

I had a Hotmail account years before Gmail was around, and I still keep it around just in case I get an important email there. I tried to use Gmail's "Get mail from other accounts" feature, but it always said "connection timed out". I had just assumed Hotmail offered POP3 - it doesn't. I found a helpful article on LifeHacker to get around this issue, but the software they mention is not a free service.

But, buried in the comments is a link to the "GetMail for Hotmail" Windows service. That link, however, sends you to the vendor's Web site, which wants you to sign up for a free trial of some other service in order to download the software. But, this Download.com link will let you download it with no strings attached.

You do have to enter your Hotmail password so that it can retreive the mail, which may be a deal-breaker for some. To be safer, I changed my Hotmail password to make sure it doesn't match any other service I use. I'm also confident that I don't use this account for any service I use, though that still doesn't technically make me 100% protected.
I'm also keeping an eye on it from a network perpective to make sure it's not trying to do anything it's not supposed to. I may actually attempt to write my own simple version of this some day just for the extra piece of mind. The program also is a resource hog, so you don't want have it set to fetch mail too often.

It does work well, otherwise, though. I've got mail set up to go directly to my Gmail archive (Settings --> Filters), and labeled with "Hotmail", so it's separate from my regular Gmail mail. I also set up my Hotmail.com account under "Settings --> Send mail as" so that I can have email sent as if it came from Hotmail.

If everything keeps working well, I'll be very glad to not have to log into Hotmail anymore.

Thursday, July 24, 2008

SQL Server 2000 not deleting backups

I'm sure there could be a lot more reasons for backup deletions failing than this, but detaching a database that was set to "offline" fixed the problem for me. No other error messages I could find - luckily I realized the problem started around the time we took that db offline, so it didn't take too long to troubleshoot.

Monday, July 14, 2008

JTabbedPane with close button that sets the tab title correctly

Java 6 includes the abilility to add a component (like a "x" to close the tab) to a tab pane, and Alexander Potochkin, has elegantly put together a "ButtonTabComponent" class to easily handle this.

I've made just a few changes to get the "x" to look more like the "x"'s on the rest of my application, and most importantly for me, to set the title of the JTabbedPane so that calling getTitleAt(tabPosition) doesn't return an empty string, since this is now handled completely by the ButtonTabComponent class. BTW, I'm mixing regular tabs with tabs that can be closed, so I want to be able to just have to call one method to check what each's title is, i.e.:

int existingTabPosition = 0;
int tabCount = tabbedPane.getTabCount();
for(int i = 0; i < tabCount; i++) {
String thisTabTitle = tabbedPane.getTitleAt(i);
if(thisTabTitle.equals(menuSelectedTabTitle)) {
existingTabPosition = i;
}
}

if(existingTabPosition != 0) {
//report already exists - just force focus to it
tabbedPane.setSelectedIndex(existingTabPosition);
} else {
//no report exists by this name - add new tab
tabbedPane.add(jrView);
int newTabPosition = tabbedPane.getTabCount() - 1;

tabbedPane.setTabComponentAt(newTabPosition, new ButtonTabComponent(menuSelectedTabTitle, tabbedPane));
tabbedPane.setSelectedIndex(newTabPosition);
}

Here's my updated "paintComponent" method of the TabButton inner class:
      //paint the "x" and update the title
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Stroke stroke = g2.getStroke();

//shift the image for pressed buttons
//changed by DG
//if(!getModel().isPressed()) {
// g2.translate(-1, -1);
//}

g2.setStroke(new BasicStroke(2));
g.setColor(Color.BLACK);
if(getModel().isRollover()) {
Color highlight = new Color(0, 51, 153); //dark blue - changed by DG
g.setColor(highlight);
}
int delta = 5; //changed by DG
g.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1);
g.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1);

//NOTE: only needed if button pressed in effect is used above
//changed by DG
//leave the graphics unchanged
//if(!getModel().isPressed()) {
// g.translate(1, 1);
//}
g2.setStroke(stroke);

//add the title here - added by DG
int i = pane.indexOfTabComponent(ButtonTabComponent.this);
pane.setTitleAt(i, label.getText());
}
I assume the title is set here for a specific reason, though my next experiment will be to remove the title from here and add it back to the JTabbedPane - though the way it is might actually be a bit nicer to work with. Alternatively, you can set both at the same time - it works without problems, but it seems like bad form to have to manually set the same thing twice.