PKCLsoft
Where great things start…
  • Home
  • Blog
  • Money Up Credits
  • Crazy Balloons – Word List Gallery
  • Tap Times Tables
    • Times Tables Reports
    • Reviews
    • Suggestions and Credits
  • uAlertMe for iAlertU
    • Using uAlertMe for the first time
    • Network Settings
    • Other settings
    • Ready to Connect
    • Connected!
    • Configuring your Mac
  • Support
    • Crash reporting
  • Privacy
    • COPPA in the US
  • World of Hex Press Kit
Select Page ...

Monthly: May, 2011

My own CCAnimatedSprite class.

pkclsoft May 28, 2011 CCAnimatedSprite, CCSprite, cocos2d-iphone, sample code, ualertme iphone

As part of the ongoing development of my current game project (using cocos2d-iphone), I recently found myself facing yet another clone of the code required to animate an object within a scene.

Whilst it’s so easy to just grab code, plonk it in and modify it to suit your purposes, it’s a good idea to stop yourself every now and then and assess the damage.

I was finding that the code was becoming untidy, and it was time to do some refactoring.  So, having recognised that all of my animated sprites were essentially being created the same way, I set about creating my CCAnimatedSprite class which is a subclass of CCSprite.

What it does is provide an easy to use constructor (to borrow a Java term) that, given the “name” of the spritesheet that contains the images that make up the animation, sets up the sprite object , all ready to go.  From there its very simple to request an action object that can be used on it’s own, or as a part of a more complicated suite of actions.

So, to create an animated sprite, all you need is:<

CCAnimatedSprite *animSprite1 = [CCAnimatedSprite nodeWithSheetName:@"anim" andFrameCount:15];


What this does is create a CCSprite object that contains all the information it needs to animate 15 frames from a spritesheet called “anim_sheet.png”.

So, for this animated sprite there are two files it expects to find:

  1. “anim_sheet.plist” which is a standard cocos2d spritesheet description file.
  2. “anim_sheet.png” which is the spritesheet itself.
As you may have guessed, the names of the files are constructed using the sheet name passed into the constructor.

In additon to this, each frame within the animation is expected to have a name that takes the form:

   _%02d.png

So this effectively means the animation will support up to 99 frames, where each frame name begins with the sheet name.  Using the example above, a 5 frame animation would contain frames called:

  anim_01.png

  anim_02.png

  anim_03.png
  anim_04.png
  anim_05.png
To use the animation, it can be as simple as:
CCAnimatedSprite *animSprite1 = [CCAnimatedSprite nodeWithSheetName:@"anim"
andFrameCount:15];
[animSprite1 setPosition:CGPointMake(300.0, 257.0)];
[self addChild:animSprite1 z:10];
[animSprite1 animate];

where the call to [animSprite1 animate] essentially tells the animation to run forever.

If you need to embed the animation within a suite of actions that have the sprite moving on the screen in some fashion, you can use the “animationAction” method to get a CCAnimate action object:

CCAnimatedSprite *animSprite2 = [CCAnimatedSprite nodeWithSheetName:@"anim" 
andFrameCount:15];
[animSprite2 setPosition:CGPointMake(20.0, 157.0)];
[animSprite2 setScale:3.0];
[self addChild:animSprite2 z:10];
[animSprite2 runAction:[CCRepeatForever actionWithAction:
[CCSpawn actions:
[CCRepeat actionWithAction:[animSprite2 animationAction] times:6],
[CCSequence actions:
[CCMoveTo actionWithDuration:2.5 position:CGPointMake(880.0, 157.0)],
[CCMoveTo actionWithDuration:2.5 position:CGPointMake(20.0, 157.0)],
nil],
nil]]];

You can get the code and a very simple sample project from: http://www.pkclsoft.com/downloads/AnimatedSprite.zip

>My own CCAnimatedSprite class.

admin May 28, 2011 CCAnimatedSprite, CCSprite, cocos2d-iphone, sample code, ualertme iphone

>As part of the ongoing development of my current game project (using cocos2d-iphone), I recently found myself facing yet another clone of the code required to animate an object within a scene.

Whilst it’s so easy to just grab code, plonk it in and modify it to suit your purposes, it’s a good idea to stop yourself every now and then and assess the damage.

I was finding that the code was becoming untidy, and it was time to do some refactoring.  So, having recognised that all of my animated sprites were essentially being created the same way, I set about creating my CCAnimatedSprite class which is a subclass of CCSprite.

What it does is provide an easy to use constructor (to borrow a Java term) that, given the “name” of the spritesheet that contains the images that make up the animation, sets up the sprite object , all ready to go.  From there its very simple to request an action object that can be used on it’s own, or as a part of a more complicated suite of actions.

So, to create an animated sprite, all you need is:<

CCAnimatedSprite *animSprite1 = [CCAnimatedSprite nodeWithSheetName:@"anim" andFrameCount:15];


What this does is create a CCSprite object that contains all the information it needs to animate 15 frames from a spritesheet called “anim_sheet.png”.

So, for this animated sprite there are two files it expects to find:

  1. “anim_sheet.plist” which is a standard cocos2d spritesheet description file.
  2. “anim_sheet.png” which is the spritesheet itself.
As you may have guessed, the names of the files are constructed using the sheet name passed into the constructor.

In additon to this, each frame within the animation is expected to have a name that takes the form:

   <sheet name>_%02d.png

So this effectively means the animation will support up to 99 frames, where each frame name begins with the sheet name.  Using the example above, a 5 frame animation would contain frames called:

  anim_01.png

  anim_02.png

  anim_03.png
  anim_04.png
  anim_05.png
To use the animation, it can be as simple as:
CCAnimatedSprite *animSprite1 = [CCAnimatedSprite nodeWithSheetName:@"anim"
andFrameCount:15];
[animSprite1 setPosition:CGPointMake(300.0, 257.0)];
[self addChild:animSprite1 z:10];
[animSprite1 animate];

where the call to [animSprite1 animate] essentially tells the animation to run forever.

If you need to embed the animation within a suite of actions that have the sprite moving on the screen in some fashion, you can use the “animationAction” method to get a CCAnimate action object:

CCAnimatedSprite *animSprite2 = [CCAnimatedSprite nodeWithSheetName:@"anim" 
andFrameCount:15];
[animSprite2 setPosition:CGPointMake(20.0, 157.0)];
[animSprite2 setScale:3.0];
[self addChild:animSprite2 z:10];
[animSprite2 runAction:[CCRepeatForever actionWithAction:
[CCSpawn actions:
[CCRepeat actionWithAction:[animSprite2 animationAction] times:6],
[CCSequence actions:
[CCMoveTo actionWithDuration:2.5 position:CGPointMake(880.0, 157.0)],
[CCMoveTo actionWithDuration:2.5 position:CGPointMake(20.0, 157.0)],
nil],
nil]]];

You can get the code and a very simple sample project from: http://www.pkclsoft.com/downloads/AnimatedSprite.zip

Scrolling Menus on Cocos2D

pkclsoft May 10, 2011 cocos2d scrolling menu CCMenu sample code, cocos2d-iphone

A short time ago I was working on a scene within a new game I’m writing.  This scene required the display of a variable length list of items that could be tapped.


I soon found that the CCMenu class didn’t really support the concept of scrolling, and after much hunting around I decided that I’d have to do it myself.


The end result is a small, reusable class that accepts 4 parameters:

  1. The name of a background image.
  2. The name of a foreground image.
  3. A CGRect that defines the area within which the menu will appear.
  4. An array of NSString objects that form the menu item labels.
The code to create and display a menu is as simple as:

- (void) showScrollingMenu:(CCMenuItemLabel*)item {
ScrollingMenuScene *ns = 
[ScrollingMenuScene nodeWithForeground:@"foreground.png"
andBackground:@"background.png"
andRect:CGRectMake(50.0, 40.0, 200.0, 260.0)
andItems:[NSArray arrayWithObjects:
@"First", @"Second", @"Third", @"Fourth",
@"Fifth", @"Sixth", @"Seventh", @"Eighth",
@"Ninth", @"Tenth", @"Eleventh", @"Twelfth",
@"Thirteenth", @"Fourteenth", @"Fifteenth", @"Sixteenth",
@"Seventeenth", @"Eighteenth", @"Nineteenth", @"Twentieth",
nil]];
[[CCDirector sharedDirector] replaceScene:ns];
}

To achieve the appearance of a scrolling menu, the class places the background at the bottom, a layer above this containing the menu, and the foreground image on top.

It is assumed that the foreground image has a transparent “window” that matches the rectangular area defined by the CGRect parameter.

You can get the code, and a sample project that demonstrates it from www.pkclsoft.com/downloads/ScrollingMenuSample.zip
As it turned out, I didn’t end up using the class in my project, but it’s handy to have it around.  At the moment it only uses textual menu items, but there’s no reason why it couldn’t be adapted to use other menu item classes.

>Scrolling Menus on Cocos2D

admin May 10, 2011 cocos2d scrolling menu CCMenu sample code, cocos2d-iphone

>A short time ago I was working on a scene within a new game I’m writing.  This scene required the display of a variable length list of items that could be tapped.


I soon found that the CCMenu class didn’t really support the concept of scrolling, and after much hunting around I decided that I’d have to do it myself.


The end result is a small, reusable class that accepts 4 parameters:

  1. The name of a background image.
  2. The name of a foreground image.
  3. A CGRect that defines the area within which the menu will appear.
  4. An array of NSString objects that form the menu item labels.
The code to create and display a menu is as simple as:

- (void) showScrollingMenu:(CCMenuItemLabel*)item {
ScrollingMenuScene *ns = 
[ScrollingMenuScene nodeWithForeground:@"foreground.png"
andBackground:@"background.png"
andRect:CGRectMake(50.0, 40.0, 200.0, 260.0)
andItems:[NSArray arrayWithObjects:
@"First", @"Second", @"Third", @"Fourth",
@"Fifth", @"Sixth", @"Seventh", @"Eighth",
@"Ninth", @"Tenth", @"Eleventh", @"Twelfth",
@"Thirteenth", @"Fourteenth", @"Fifteenth", @"Sixteenth",
@"Seventeenth", @"Eighteenth", @"Nineteenth", @"Twentieth",
nil]];
[[CCDirector sharedDirector] replaceScene:ns];
}

To achieve the appearance of a scrolling menu, the class places the background at the bottom, a layer above this containing the menu, and the foreground image on top.

It is assumed that the foreground image has a transparent “window” that matches the rectangular area defined by the CGRect parameter.

You can get the code, and a sample project that demonstrates it from www.pkclsoft.com/downloads/ScrollingMenuSample.zip
As it turned out, I didn’t end up using the class in my project, but it’s handy to have it around.  At the moment it only uses textual menu items, but there’s no reason why it couldn’t be adapted to use other menu item classes.

uAlertMe - the good the bad and the ugly

pkclsoft May 7, 2011 ualertme iphone

So, uAlertMe has been on sale for just over week now.  Where do we stand?  What works, what doesn’t, and what needs improving?


The good:
Overall, the feedback I’m getting is that it does what I intended.  It works, and most people seem happy.


The bad:
Well I haven’t really had any negative feedback so far that I would consider bad.  There have been no crash reports sent through which is great.  One user had trouble connecting to his Mac, but that sort of thing is problematic at best when you consider firewalls, and routers, etc.


The ugly:
One user has commented that it’s a bit ugly to look at, and that it should be a free app with iAd integrated.  Yes, my graphics are probably a bit rough, but I thought that for a utility app it looked OK.   


There are people out there willing to create great graphics for me, but they cost, and given that I never expected to get rich off uAlertMe I thought that what I had done was reasonable.


What do you think?  Please leave a comment here if there is something you’d like to see added, or changed.   It’s early days yet, and I obviously have some marketing lessons to go through, but I’m keen to improve the app where I can.


So, let me know.

Java Send Mail Client

pkclsoft May 6, 2011 ialertu jsendmail mail client, ualertme iphone

For some time now I’ve been working on the open-source project called iAlertU, found at: sourceforge.net/projects/ialertu/.


One of the first things I did on the project was change the way it sent email.  I didn’t like the dependence on the user having to use Apple Mail for their email.  I guess I’m a Thunderbird user from way back.


A lot of people these days just use online email such as Gmail.  I do, if for no other reason than their wonderful anti-spam filters.


So, in order to make iAlertU independant of any specific email client, I wrote what started out as a fairly simple client in Java.  This started out as a snippet of example code from Sun, that was integrated into my PortaBill application, but over the past year or so, it has grown a bit in complexity in order to meet the needs of iAlertU and the growing community of Mac users out that that are installing it.


As users of iAlertU have pointed out weaknesses in the email client due to the many and varied email configurations, jsendmail has grown in features. It’s still only a single Java class, and it’s not a lot of code, but it represents many hours of searching and debugging to try and get a client that can do everything I need.


jsendmail is a simple console application.  iAlertU runs it behind the scenes, passing arguments to tell it where to find the email body and any images that need to be sent.  It can also be used independently.


The original command syntax was modelled on mini_sendmail, and similar linux client that I use occasionally.


So, with the gentle prompting of an iAlertU user, I decided to post the code here.  If there’s enough interest, I’ll put it on sourceforge.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;
import java.util.Vector;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

/**
* @author Peter Easdown
* http://www.pkclsoft.com
* @version 1.1
*
*/
public class JSendmail {

private static final int SMTP = 0;
private static final int SMTPS = 1;

private static Vector ccList = new Vector();
private static Vector attachmentList = new Vector();
private static String toAddr;
private static String subject;
private static String serverName;
private static File messageFile;
private static InputStream input;
private static String username;
private static String password;
private static String fromAddress;
private static String contentType;
private static int serverPort = -1;
private static int smtpMode = SMTP;

private static final String SMTP_TITLE[] = {"smtp", "smtps"};

/**
* Validates command line parameters and executes main function if valid.
*
* @param args - The command line parameters
*/
public static void main(String[] args) {
// Process the arguments.
if (args.length < 5) {
printUsage("Too few arguments.");
}

int argIndex = 0;

while ((argIndex < args.length) && (serverName == null)) {
if (args[argIndex].equals("-f")) {
argIndex++;

if (messageFile != null) {
printUsage("Only one instance of \"-f\" option allowed");
} else if (argIndex < args.length) {
messageFile = new File(args[argIndex]);

if (!messageFile.exists()) {
printUsage("-f filename <" + args[argIndex] + "> does not exist!");
}

argIndex++;
} else {
printUsage("-f requires filename parameter");
}
} else if (args[argIndex].equals("-s")) {
argIndex++;

if (subject != null) {
printUsage("Only one instance of \"-s\" option allowed");
} else if (argIndex < args.length) {
subject = args[argIndex++];
} else {
printUsage("-s requires string parameter");
}
} else if (args[argIndex].equals("-c")) {
argIndex++;

if (ccList.contains(args[argIndex])) {
printUsage("CC address: " + args[argIndex] + " is specified more than once");
} else if (argIndex < args.length) {
String newAddr = args[argIndex++];

if (newAddr.indexOf('@') == -1) {
printUsage("CC address is invalid, must contain \"@\"");
}
ccList.add(newAddr);
} else {
printUsage("-c requires string parameter");
}
} else if (args[argIndex].equals("-a")) {
argIndex++;

if (attachmentList.contains(args[argIndex])) {
printUsage("attachment: " + args[argIndex] + " is specified more than once");
} else if (argIndex < args.length) {
File newAttachment = new File(args[argIndex]);

if (!newAttachment.exists()) {
printUsage("attachment: " + args[argIndex] + " does not exist.");
} else if (newAttachment.isDirectory()) {
printUsage("attachment: " + args[argIndex] + " must not be a directory.");
} else {
attachmentList.add(newAttachment);
}

argIndex++;
} else {
printUsage("-a requires string parameter");
}
} else if (args[argIndex].equals("-u")) {
argIndex++;

if (username == null) {
if (argIndex < args.length) {
username = args[argIndex++];
} else {
printUsage("-u requires string parameter");
}
} else {
printUsage("-u may only be specified once");
}
} else if (args[argIndex].equals("-p")) {
argIndex++;

if (password == null) {
if (argIndex < args.length) {
password = args[argIndex++];
} else {
printUsage("-p requires string parameter");
}
} else {
printUsage("-p may only be specified once");
}
} else if (args[argIndex].equals("-sp")) {
argIndex++;

if (serverPort == -1) {
if (argIndex < args.length) {
try {
serverPort = Integer.parseInt(args[argIndex++]);
} catch (Exception e) {
printUsage("-sp requires integer parameter");
}
} else {
printUsage("-sp requires integer parameter");
}
} else {
printUsage("-sp may only be specified once");
}
} else if (args[argIndex].equals("-ct")) {
argIndex++;

if (argIndex < args.length) {
contentType = args[argIndex++];
}
} else if (args[argIndex].equals("-ssl")) {
argIndex++;

if (smtpMode == SMTPS) {
printUsage("-ssl may only be specified once");
} else {
smtpMode = SMTPS;
}
} else if (args[argIndex].equals("-from")) {
argIndex++;

if (fromAddress == null) {
if (argIndex < args.length) {
fromAddress = args[argIndex++];
} else {
printUsage("-from requires string parameter");
}
} else {
printUsage("-from may only be specified once");
}
} else if (toAddr == null) {
toAddr = args[argIndex++];

if (toAddr.indexOf('@') == -1) {
printUsage("send to address (" + toAddr + ") is invalid, must contain \"@\"");
}
} else if (serverName == null) {
serverName = args[argIndex++];

if (fromAddress == null) {
fromAddress = System.getProperty("user.name") + "@" + serverName;
}
}
}

if (messageFile != null) {
try {
input = new FileInputStream(messageFile);
}
catch (FileNotFoundException e) {
printUsage("Unable to open file: " + messageFile.getPath());
}
} else {
input = System.in;
}

if (username == null) {
// Default the username.
//
username = System.getProperty("user.name");
}

if ((toAddr == null) || (serverName == null) ||
(username == null) || (password == null)) {
printUsage("Not enough arguments");
}

if (serverPort == -1) {
serverPort = 25;
}

if (contentType == null) {
contentType = "text/plain";
}

new JSendmail();
}

private static void printUsage(String reason) {
System.out.println("JSendmail: " + reason);
System.out.println("");
System.out.println("JSendmail usage:");
System.out.println("");
System.out.println("");
System.out.println(" JSendmail [-f filename] [-s \"str\"] [-c ccaddress] [-a attachment]");
System.out.println(" [-from fromaddress] [-u username] [-p password]");
System.out.println(" [-sp port] [-ssl] toaddress viaserver \"text\"");
System.out.println("");
System.out.println(" -f filename - Specifies that the text of the message be read from the file \"filename\" instead");
System.out.println(" of standard input.");
System.out.println(" -s \"str\" - Specifies the subject of the email to be sent.");
System.out.println(" -c ccaddress - The email address of some to add to the CC list.");
System.out.println(" -a attachment - The path of an attachment to add to the email.");
System.out.println(" toaddress - The email address to which the email will be sent.");
System.out.println(" viaserver - The hostname of the mail server via which the email will be sent.");
System.out.println(" -u username - the username with which to authenticate, defaults to currently logged in username.");
System.out.println(" -p password - the password with which to authenticate.");
System.out.println(" -sp port - the port number to connect with.");
System.out.println(" -ssl - Instructs jsendmail to use SSL when connecting to the SMTP server.");
System.out.println(" -ct type - the content type of the email, defaults to \"text/plain\"");
System.out.println("");
System.out.println(" \"text\" - the actual text of the message as read from standard input.");
System.out.println("");
System.out.println("Note that if no -fromaddress option is specified, then the currently logged in username is used");
System.out.println("in the form @viaserver.");
System.out.println("");
System.exit(1);
}

private JSendmail() {
Properties props = System.getProperties();
// Set up the mail server.
props.put("mail." + SMTP_TITLE[smtpMode] + ".host", serverName);
props.put("mail." + SMTP_TITLE[smtpMode] + ".auth", "true");
props.put("mail.smtp.connectiontimeout", "120000");
props.put("mail.smtp.timeout", "120000");
props.put("mail.smtp.starttls.enable", "true");

boolean sent = false;
String reason = null;

Session session = Session.getInstance(props, new SMTPAuthenticator(username, password));
//session.setDebug(true);

// Create a new message --
Message msg = new MimeMessage(session);

// Set the FROM and TO fields --
try {
try {
msg.addFrom(new Address[] {new InternetAddress(fromAddress, fromAddress.substring(0, fromAddress.indexOf('@')))});

msg.addRecipient(Message.RecipientType.TO, new InternetAddress(
toAddr,
toAddr.substring(0, toAddr.indexOf('@'))));
msg.setReplyTo(new Address[] {new InternetAddress(fromAddress, fromAddress.substring(0, fromAddress.indexOf('@')))});

// -- Set the subject and body text --
if (subject != null) {
msg.setSubject(subject);
}

if (ccList.size() > 0) {
for (int i = 0; i< ccList.size(); i++) {
String ccAddr = (String)ccList.elementAt(i);
msg.addRecipient(Message.RecipientType.CC, new InternetAddress(ccAddr, ccAddr.substring(0, ccAddr.indexOf('@'))));
}
}

// -- Set some other header information --
msg.setSentDate(new Date());

byte[] bytes = new byte[input.available()];
input.read(bytes, 0, input.available());

if (attachmentList.isEmpty()) {
msg.setContent(new String(bytes), contentType);
} else {
// Create the message part
BodyPart messageBodyPart = new MimeBodyPart();

// Fill the message
messageBodyPart.setContent(new String(bytes), contentType);

Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);

// Part two is attachment
for (int i =0; i < attachmentList.size(); i++) {
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(attachmentList.elementAt(i));
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(attachmentList.elementAt(i).getName());
messageBodyPart.setHeader("Content-ID","");
multipart.addBodyPart(messageBodyPart);
}

// Put parts in message
msg.setContent(multipart);
}

msg.saveChanges();

Transport trans = session.getTransport(SMTP_TITLE[smtpMode]);

try {
// -- Send the message --
trans.connect(serverName, serverPort, username, password);
trans.sendMessage(msg, msg.getAllRecipients());
} finally {
trans.close();
}

sent = true;
} catch (UnsupportedEncodingException e1) {
sent = false;
reason = e1.getMessage();
} catch (IOException e) {
sent = false;
reason = e.getMessage();
}
} catch (AddressException e) {
sent = false;

if (e.getMessage() != null) {
reason = e.getMessage().trim();
} else if (e.toString() != null) {
reason = e.toString();
} else {
reason = "Unknown";
}

if (e.getCause() != null) {
reason = reason + "\n" + e.getCause().getMessage().trim();
}
} catch (MessagingException e) {
sent = false;

if (e.getMessage() != null) {
reason = e.getMessage().trim();
} else if (e.toString() != null) {
reason = e.toString();
} else {
reason = "Unknown";
}

if (e.getNextException() != null) {
if (e.getNextException().getMessage() != null) {
reason = reason + "\n" + e.getNextException().getMessage().trim();
}
}

if (e.getCause() != null) {
reason = reason + "\n" + e.getCause().getMessage().trim();
}
}

if (!sent) {
System.out.println("JSendmail unable to send: " + reason);
System.exit(1);
} else {
System.exit(0);
}
}


public class SMTPAuthenticator extends Authenticator {
private PasswordAuthentication authentic;

public SMTPAuthenticator(String username, String password) {
authentic = new PasswordAuthentication(username, password);
}

public PasswordAuthentication getPasswordAuthentication() {
return authentic;
}
}
}






>Java Send Mail Client

admin May 6, 2011 ialertu jsendmail mail client, ualertme iphone

>For some time now I’ve been working on the open-source project called iAlertU, found at: sourceforge.net/projects/ialertu/.


One of the first things I did on the project was change the way it sent email.  I didn’t like the dependence on the user having to use Apple Mail for their email.  I guess I’m a Thunderbird user from way back.


A lot of people these days just use online email such as Gmail.  I do, if for no other reason than their wonderful anti-spam filters.


So, in order to make iAlertU independant of any specific email client, I wrote what started out as a fairly simple client in Java.  This started out as a snippet of example code from Sun, that was integrated into my PortaBill application, but over the past year or so, it has grown a bit in complexity in order to meet the needs of iAlertU and the growing community of Mac users out that that are installing it.


As users of iAlertU have pointed out weaknesses in the email client due to the many and varied email configurations, jsendmail has grown in features. It’s still only a single Java class, and it’s not a lot of code, but it represents many hours of searching and debugging to try and get a client that can do everything I need.


jsendmail is a simple console application.  iAlertU runs it behind the scenes, passing arguments to tell it where to find the email body and any images that need to be sent.  It can also be used independently.


The original command syntax was modelled on mini_sendmail, and similar linux client that I use occasionally.


So, with the gentle prompting of an iAlertU user, I decided to post the code here.  If there’s enough interest, I’ll put it on sourceforge.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;
import java.util.Vector;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

/**
* @author Peter Easdown
* http://www.pkclsoft.com
* @version 1.1
*
*/
public class JSendmail {

private static final int SMTP = 0;
private static final int SMTPS = 1;

private static Vector ccList = new Vector();
private static Vector attachmentList = new Vector();
private static String toAddr;
private static String subject;
private static String serverName;
private static File messageFile;
private static InputStream input;
private static String username;
private static String password;
private static String fromAddress;
private static String contentType;
private static int serverPort = -1;
private static int smtpMode = SMTP;

private static final String SMTP_TITLE[] = {"smtp", "smtps"};

/**
* Validates command line parameters and executes main function if valid.
*
* @param args - The command line parameters
*/
public static void main(String[] args) {
// Process the arguments.
if (args.length < 5) {
printUsage("Too few arguments.");
}

int argIndex = 0;

while ((argIndex < args.length) && (serverName == null)) {
if (args[argIndex].equals("-f")) {
argIndex++;

if (messageFile != null) {
printUsage("Only one instance of \"-f\" option allowed");
} else if (argIndex < args.length) {
messageFile = new File(args[argIndex]);

if (!messageFile.exists()) {
printUsage("-f filename <" + args[argIndex] + "> does not exist!");
}

argIndex++;
} else {
printUsage("-f requires filename parameter");
}
} else if (args[argIndex].equals("-s")) {
argIndex++;

if (subject != null) {
printUsage("Only one instance of \"-s\" option allowed");
} else if (argIndex < args.length) {
subject = args[argIndex++];
} else {
printUsage("-s requires string parameter");
}
} else if (args[argIndex].equals("-c")) {
argIndex++;

if (ccList.contains(args[argIndex])) {
printUsage("CC address: " + args[argIndex] + " is specified more than once");
} else if (argIndex < args.length) {
String newAddr = args[argIndex++];

if (newAddr.indexOf('@') == -1) {
printUsage("CC address is invalid, must contain \"@\"");
}
ccList.add(newAddr);
} else {
printUsage("-c requires string parameter");
}
} else if (args[argIndex].equals("-a")) {
argIndex++;

if (attachmentList.contains(args[argIndex])) {
printUsage("attachment: " + args[argIndex] + " is specified more than once");
} else if (argIndex < args.length) {
File newAttachment = new File(args[argIndex]);

if (!newAttachment.exists()) {
printUsage("attachment: " + args[argIndex] + " does not exist.");
} else if (newAttachment.isDirectory()) {
printUsage("attachment: " + args[argIndex] + " must not be a directory.");
} else {
attachmentList.add(newAttachment);
}

argIndex++;
} else {
printUsage("-a requires string parameter");
}
} else if (args[argIndex].equals("-u")) {
argIndex++;

if (username == null) {
if (argIndex < args.length) {
username = args[argIndex++];
} else {
printUsage("-u requires string parameter");
}
} else {
printUsage("-u may only be specified once");
}
} else if (args[argIndex].equals("-p")) {
argIndex++;

if (password == null) {
if (argIndex < args.length) {
password = args[argIndex++];
} else {
printUsage("-p requires string parameter");
}
} else {
printUsage("-p may only be specified once");
}
} else if (args[argIndex].equals("-sp")) {
argIndex++;

if (serverPort == -1) {
if (argIndex < args.length) {
try {
serverPort = Integer.parseInt(args[argIndex++]);
} catch (Exception e) {
printUsage("-sp requires integer parameter");
}
} else {
printUsage("-sp requires integer parameter");
}
} else {
printUsage("-sp may only be specified once");
}
} else if (args[argIndex].equals("-ct")) {
argIndex++;

if (argIndex < args.length) {
contentType = args[argIndex++];
}
} else if (args[argIndex].equals("-ssl")) {
argIndex++;

if (smtpMode == SMTPS) {
printUsage("-ssl may only be specified once");
} else {
smtpMode = SMTPS;
}
} else if (args[argIndex].equals("-from")) {
argIndex++;

if (fromAddress == null) {
if (argIndex < args.length) {
fromAddress = args[argIndex++];
} else {
printUsage("-from requires string parameter");
}
} else {
printUsage("-from may only be specified once");
}
} else if (toAddr == null) {
toAddr = args[argIndex++];

if (toAddr.indexOf('@') == -1) {
printUsage("send to address (" + toAddr + ") is invalid, must contain \"@\"");
}
} else if (serverName == null) {
serverName = args[argIndex++];

if (fromAddress == null) {
fromAddress = System.getProperty("user.name") + "@" + serverName;
}
}
}

if (messageFile != null) {
try {
input = new FileInputStream(messageFile);
}
catch (FileNotFoundException e) {
printUsage("Unable to open file: " + messageFile.getPath());
}
} else {
input = System.in;
}

if (username == null) {
// Default the username.
//
username = System.getProperty("user.name");
}

if ((toAddr == null) || (serverName == null) ||
(username == null) || (password == null)) {
printUsage("Not enough arguments");
}

if (serverPort == -1) {
serverPort = 25;
}

if (contentType == null) {
contentType = "text/plain";
}

new JSendmail();
}

private static void printUsage(String reason) {
System.out.println("JSendmail: " + reason);
System.out.println("");
System.out.println("JSendmail usage:");
System.out.println("");
System.out.println("");
System.out.println(" JSendmail [-f filename] [-s \"str\"] [-c ccaddress] [-a attachment]");
System.out.println(" [-from fromaddress] [-u username] [-p password]");
System.out.println(" [-sp port] [-ssl] toaddress viaserver \"text\"");
System.out.println("");
System.out.println(" -f filename - Specifies that the text of the message be read from the file \"filename\" instead");
System.out.println(" of standard input.");
System.out.println(" -s \"str\" - Specifies the subject of the email to be sent.");
System.out.println(" -c ccaddress - The email address of some to add to the CC list.");
System.out.println(" -a attachment - The path of an attachment to add to the email.");
System.out.println(" toaddress - The email address to which the email will be sent.");
System.out.println(" viaserver - The hostname of the mail server via which the email will be sent.");
System.out.println(" -u username - the username with which to authenticate, defaults to currently logged in username.");
System.out.println(" -p password - the password with which to authenticate.");
System.out.println(" -sp port - the port number to connect with.");
System.out.println(" -ssl - Instructs jsendmail to use SSL when connecting to the SMTP server.");
System.out.println(" -ct type - the content type of the email, defaults to \"text/plain\"");
System.out.println("");
System.out.println(" \"text\" - the actual text of the message as read from standard input.");
System.out.println("");
System.out.println("Note that if no -fromaddress option is specified, then the currently logged in username is used");
System.out.println("in the form @viaserver.");
System.out.println("");
System.exit(1);
}

private JSendmail() {
Properties props = System.getProperties();
// Set up the mail server.
props.put("mail." + SMTP_TITLE[smtpMode] + ".host", serverName);
props.put("mail." + SMTP_TITLE[smtpMode] + ".auth", "true");
props.put("mail.smtp.connectiontimeout", "120000");
props.put("mail.smtp.timeout", "120000");
props.put("mail.smtp.starttls.enable", "true");

boolean sent = false;
String reason = null;

Session session = Session.getInstance(props, new SMTPAuthenticator(username, password));
//session.setDebug(true);

// Create a new message --
Message msg = new MimeMessage(session);

// Set the FROM and TO fields --
try {
try {
msg.addFrom(new Address[] {new InternetAddress(fromAddress, fromAddress.substring(0, fromAddress.indexOf('@')))});

msg.addRecipient(Message.RecipientType.TO, new InternetAddress(
toAddr,
toAddr.substring(0, toAddr.indexOf('@'))));
msg.setReplyTo(new Address[] {new InternetAddress(fromAddress, fromAddress.substring(0, fromAddress.indexOf('@')))});

// -- Set the subject and body text --
if (subject != null) {
msg.setSubject(subject);
}

if (ccList.size() > 0) {
for (int i = 0; i< ccList.size(); i++) {
String ccAddr = (String)ccList.elementAt(i);
msg.addRecipient(Message.RecipientType.CC, new InternetAddress(ccAddr, ccAddr.substring(0, ccAddr.indexOf('@'))));
}
}

// -- Set some other header information --
msg.setSentDate(new Date());

byte[] bytes = new byte[input.available()];
input.read(bytes, 0, input.available());

if (attachmentList.isEmpty()) {
msg.setContent(new String(bytes), contentType);
} else {
// Create the message part
BodyPart messageBodyPart = new MimeBodyPart();

// Fill the message
messageBodyPart.setContent(new String(bytes), contentType);

Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);

// Part two is attachment
for (int i =0; i < attachmentList.size(); i++) {
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(attachmentList.elementAt(i));
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(attachmentList.elementAt(i).getName());
messageBodyPart.setHeader("Content-ID","");
multipart.addBodyPart(messageBodyPart);
}

// Put parts in message
msg.setContent(multipart);
}

msg.saveChanges();

Transport trans = session.getTransport(SMTP_TITLE[smtpMode]);

try {
// -- Send the message --
trans.connect(serverName, serverPort, username, password);
trans.sendMessage(msg, msg.getAllRecipients());
} finally {
trans.close();
}

sent = true;
} catch (UnsupportedEncodingException e1) {
sent = false;
reason = e1.getMessage();
} catch (IOException e) {
sent = false;
reason = e.getMessage();
}
} catch (AddressException e) {
sent = false;

if (e.getMessage() != null) {
reason = e.getMessage().trim();
} else if (e.toString() != null) {
reason = e.toString();
} else {
reason = "Unknown";
}

if (e.getCause() != null) {
reason = reason + "\n" + e.getCause().getMessage().trim();
}
} catch (MessagingException e) {
sent = false;

if (e.getMessage() != null) {
reason = e.getMessage().trim();
} else if (e.toString() != null) {
reason = e.toString();
} else {
reason = "Unknown";
}

if (e.getNextException() != null) {
if (e.getNextException().getMessage() != null) {
reason = reason + "\n" + e.getNextException().getMessage().trim();
}
}

if (e.getCause() != null) {
reason = reason + "\n" + e.getCause().getMessage().trim();
}
}

if (!sent) {
System.out.println("JSendmail unable to send: " + reason);
System.exit(1);
} else {
System.exit(0);
}
}


public class SMTPAuthenticator extends Authenticator {
private PasswordAuthentication authentic;

public SMTPAuthenticator(String username, String password) {
authentic = new PasswordAuthentication(username, password);
}

public PasswordAuthentication getPasswordAuthentication() {
return authentic;
}
}
}






iWeb Duplicate Image Optimization

pkclsoft May 2, 2011 Duplicate, Images, iWeb, optimise, optimize, Problem

About a month ago, I put together the website for pkclsoft.com.  Before I started however, I hunted around for a tool I could use on my Macbook that was free, and would do what I wanted.


There are tools around, and no matter which one you choose, they all have their pros and cons. In any case, I ended up choosing to stick with iWeb.  It’s there, and it works.


That said, I set about writing and laying out the website, importing my old Google pages site I had for PortaBill, and getting it online.


Whilst it’s a fairly simple website, it does the trick for now, but there has been one very annoying quirk; the images are all saved within the site in a folder for the respective page.


I did a bit of hunting to see if I could find some way to tell iWeb not to do this.  I couldn’t.


You see, iWeb pages, by their very nature are graphic rich, have lovely backgrounds, and look nice (with very little effort mind you).


The problem is that every image, including the background images in every page of your site, is saved on a per page basis.  So, if you have 20 pages in your site, there will be 20 copies of every image included in the page templates you use from iWeb.


Back when I first hit this problem, I found a forum (here) post discussing the problem and a possible solution, but no-one seemed to have done anything about it.


Well, now that uAlertMe is up and selling I thought it was time to write the tool myself, and as a result, we now have iWebIO (iWebImageOptimizer).  This relatively simple, free tool will allow you to tell it the name of a folder on your Mac that contains the root level index.html for your iWeb website.


That done, it checks all of the PNG, TIFF, JPEG and GIF files for duplicates (by using md5).  All images that have duplicates are then listed, and you can then click on one to see what pages are using it.


Finally, clicking on “Optimize” will, after a cautionary prompt, traverse your site, moving one copy of each duplicated image to a new “images” folder in the root folder of the site.  All other duplicates are deleted, and all .html files are updated to refer to the images/xxxx files.


It’s simple, and may have some issues with older versions of iWeb pages, or templates that I haven’t used.


It makes the following assumptions:

  1. There must be an index.html in the root folder.
  2. If an image is found in the folder “xxx_files”, then there will be an html file called “xxx.html” that will need to be updated.
  3. You have made a backup of your site.  iWebIO overwrites and deletes files in the folder tree you specify.  It does warn you, but only once.
To try it out, download it from: iWebImageOptimizer.zip
If you have any queries, let me know at: support@pkclsoft.com, or comment here.  If there’s enough demand, I might make the source available.

Have fun.

>iWeb Duplicate Image Optimization

admin May 2, 2011 Duplicate, Images, iWeb, optimise, optimize, Problem

>About a month ago, I put together the website for pkclsoft.com.  Before I started however, I hunted around for a tool I could use on my Macbook that was free, and would do what I wanted.


There are tools around, and no matter which one you choose, they all have their pros and cons. In any case, I ended up choosing to stick with iWeb.  It’s there, and it works.


That said, I set about writing and laying out the website, importing my old Google pages site I had for PortaBill, and getting it online.


Whilst it’s a fairly simple website, it does the trick for now, but there has been one very annoying quirk; the images are all saved within the site in a folder for the respective page.


I did a bit of hunting to see if I could find some way to tell iWeb not to do this.  I couldn’t.


You see, iWeb pages, by their very nature are graphic rich, have lovely backgrounds, and look nice (with very little effort mind you).


The problem is that every image, including the background images in every page of your site, is saved on a per page basis.  So, if you have 20 pages in your site, there will be 20 copies of every image included in the page templates you use from iWeb.


Back when I first hit this problem, I found a forum (here) post discussing the problem and a possible solution, but no-one seemed to have done anything about it.


Well, now that uAlertMe is up and selling I thought it was time to write the tool myself, and as a result, we now have iWebIO (iWebImageOptimizer).  This relatively simple, free tool will allow you to tell it the name of a folder on your Mac that contains the root level index.html for your iWeb website.


That done, it checks all of the PNG, TIFF, JPEG and GIF files for duplicates (by using md5).  All images that have duplicates are then listed, and you can then click on one to see what pages are using it.


Finally, clicking on “Optimize” will, after a cautionary prompt, traverse your site, moving one copy of each duplicated image to a new “images” folder in the root folder of the site.  All other duplicates are deleted, and all .html files are updated to refer to the images/xxxx files.


It’s simple, and may have some issues with older versions of iWeb pages, or templates that I haven’t used.


It makes the following assumptions:

  1. There must be an index.html in the root folder.
  2. If an image is found in the folder “xxx_files”, then there will be an html file called “xxx.html” that will need to be updated.
  3. You have made a backup of your site.  iWebIO overwrites and deletes files in the folder tree you specify.  It does warn you, but only once.
To try it out, download it from: iWebImageOptimizer.zip
If you have any queries, let me know at: support@pkclsoft.com, or comment here.  If there’s enough demand, I might make the source available.

Have fun.

You can add widget to "blog" widget area by going to Appearance > Widget

Copyright © 2012 pkclsoft.com. All Rights Reserved