Friday, August 24, 2007

Greasemonkey & Auto Updating Userscripts With Subversion

Some time ago, I wanted to create a way of auto updating my Greasemonkey userscripts so that I wouldn't have to somehow manually notify everyone when an update occured.


My scripts are kept in a subversion repository with WebDav support. So one of the first things that I did was examine the HTTP headers of my web server when I made a subversion request from my browser:



Date Fri, 24 Aug 2007 16:00:53 GMT
Server Apache/2.0.54 (Unix) DAV/2 SVN/1.2.0 mod_perl/1.999.22 Perl/v5.8.5
Etag "1390//some_script.user.js"
Accept-Ranges bytes
Content-Length 638
Keep-Alive timeout=15, max=99
Connection Keep-Alive
Content-Type text/html; charset=UTF-8

The one thing to notice is the line with the Etag text. It pretty much displays the subversion version of my script, 'some_script.user.js'. So if I could get this information when my script loads and compare it to any value that I have stored previously, then I can cause my script to update itself quite easily!


So I created a function called check4Update(options) that did just that. In addition to my function, I also used cookie management code that I slighltly modified from webreference.com (included below).


The function that I created has a json object called options with the following parameters:




    • url - the subversion URL for your userscript

    • scriptname - the name of your script [defaults to the domain that the script acts upon; this may not be desirable, so please set it!]

    • delay - the number of page refreshes to wait before nagging the user again [ defaults to 7]


    Items in bold are required and items in italics are optional



 
window.check4Update = function(options) {
var options = options || {};
if (!url) {
// we need this at least ...
return;
}
var url = options.url;
var scriptname = options.scriptname || location.host + "_script";
var delay = options.delay || 7;

// cookie_name is the name of the cookie that we will set with the
// value of version
var cookie_name = scriptname

// the version of the script. Initially, this will be null.
var version = getCookie(cookie_name);

// the name of the cookie that indicates when we should nag
// about upgrading in case the user didnt want to upgrade their script
var upgrade_cookie = scriptname+ '_DELAY_UPGRADE'

try {
GM_xmlhttpRequest({
// head command because we are only interested
// in the Headers and not the script!
method: 'HEAD',
url: url,
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey '+scriptname+'/1.0'
},
onload: function(responseDetails) {
if (responseDetails.status == 200 || responseDetails.status == 202) {
var headers = (responseDetails.responseHeaders).split('\n');
for (var i in headers) {

// go through and look for 'Etag'
if (headers[i].indexOf('Etag') != -1) {
var s = headers[i];

// create a regular expression to parse the version
var regx = /^.*Etag: \"(\d+)\/\/.*\"$/;
regx = regx.exec(s);

// here s will be our version obtained from the svn repository
s = regx[1]

// here version would be a previously set version or null
// if this is the first time running the script
if (version) {
// here we get the nag count
var count = getCookie(upgrade_cookie)

// if the nag count exists and is greater than 0, then decrement it
// and dont prompt to upgrade
if (count && count-- > 0) {
// user doesnt want to be notified
var now = new Date();
window.fixDate(now);
now.setTime(now.getTime() + 31*24*60*60*1000); // expire in a month
deleteCookie(upgrade_cookie);
setCookie(upgrade_cookie, count, now,"/", scriptname + ".com");
GM_log("Wont nag for " + count +" more reloads ....");
return;
}

// so we have found a newer version of the script prompt to upgrade
if (s > version) {
// ask the user if they wish to upgrade
// if they choose not to, then set a cookie to wait 'delay' page
// refreshes and then notify them to upgrade again
if (confirm("A newer version of this script exists!\n\tRemote Version: " +s+"\n\tYour Version: "+version+"\nWould you like to upgrade now?")) {
setCookie(upgrade_cookie, -1, null, "/", scriptname + ".com");
var now = new Date();
window.fixDate(now);
now.setTime(now.getTime() + 31*24*60*60*1000); // expire in a month
deleteCookie(cookie_name,"/", scriptname + ".com");
setCookie(cookie_name, s, now,"/", scriptname + ".com");
alert("Please reload the page once the script has been updated!")
// here we cause the page to refresh with the location of our script
// this will cause greasemonkey to prompt the user to install the script
location.replace(url)
} else {
// set a cookie to upgrade later
var now = new Date();
window.fixDate(now);
now.setTime(now.getTime() + 31*24*60*60*1000); // expire in a month
deleteCookie(upgrade_cookie,"/", scriptname + ".com");
setCookie(upgrade_cookie, delay, now,"/", scriptname + ".com");
GM_log("Won't notify of upgrades for a "+delay+" page refreshes!");
}
}
} else {
// version wasnt previously set so we set the version in the cookie to
// be the current version of the script in svn repository
var now = new Date();
window.fixDate(now);
now.setTime(now.getTime() + 31*24*60*60*1000); // expire in a month
deleteCookie(cookie_name,"/", scriptname + ".com");
setCookie(cookie_name, s, now,"/", scriptname + ".com");
}
}
}
}
},
onerror: function(responseDetails) {
console.log('GM_xmlhttpRequest error: %s', responseDetails.responseText)
},
onreadystatechange: function(responseDetails) {

}
});
} catch (exception){
console.log('Error occurred while updating the script %s . The error was: %s', scriptname, exception);
}

}

The code above is commented to indicate the logic behind it. If you need more information, please add a comment and I will explain any discrepancies. Also, if you can think of a better way of doing things, please let me know!


Below are the cookie management functions:



window.setCookie = function (name, value, expires, path, domain, secure) {
var curCookie = name + "=" + escape(value) +
((expires) ? "; expires=" + expires.toGMTString() : "") +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
((secure) ? "; secure" : "");

document.cookie = curCookie;
}


window.getCookie = function (name) {
var dc = document.cookie;
var prefix = name + "=";
var begin = dc.indexOf("; " + prefix);
if (begin == -1) {
begin = dc.indexOf(prefix);
if (begin != 0) return null;
} else
begin += 2;
var end = document.cookie.indexOf(";", begin);
if (end == -1)
end = dc.length;

return unescape(dc.substring(begin + prefix.length, end));
}


window.deleteCookie = function (name, path, domain) {
if (getCookie(name)) {
document.cookie = name + "=" +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
"; expires=Thu, 01-Jan-70 00:00:01 GMT";
}
}


window.fixDate = function (date) {
var base = new Date(0);
var skew = base.getTime();
if (skew > 0)
date.setTime(date.getTime() - skew);
}


This is how one could call the this function from within their userscript.



window.check4Update({'url':'http://someurl.com/my_script.user.js', 'scriptname':'my_script_name', 'delay':7});

Finally, all of the code mentioned above was placed in my Greasemonkey userscript at the end of the onload event handler:



window.addEventListener('load',
function () {
//... your user script code here ... //

// ... the auto update code here ... //
}
,true);


Good luck!

Wednesday, August 01, 2007

Creating a Modular 'Waiting' dialog in javascript

Have you ever needed to create a modular pop up that displayed a message to the user while you did some processing in the background? I have and I got fed up of constantly creating a new modular pop up each time.

The following javascript code is easy to use and is based upon the grayOut javascript function.

This snippet of code creates a modal dialog box. This is useful for times when you want to make the user wait while you do something in the background or if you would like to make the user aware of something on the page. While the dialog box is active, most interactive elements on the page (links, etc) are inactive.

The dialog box is dynamically created and can be customized to a certain height and width. The dialog box has a high z-index and should be placed above most other elements. Items which have a higher z-index than the dialog box will appear on top of the dialog box.

By default the dialog box has a z-index of 100.

Other default parameters are:

  • height - 25% of the page height
  • width - 35% of the page width
  • opacity - 90% (i.e. semi-transparent)
  • border style - solid
  • border color - black
  • border width - 3px
  • text alignment - center
  • many others ...
All of the above can be configured to your liking. Look at the comments in the script for more customization features.

Example. This snippet has been tested in Firefox 2, Safari 3, and Internet Explorer 7.


function modular_waiting(vis, msg, options) {
// Pass true to show the dialogue screen, false to hide
// options are optional. This is a JSON object with the following (optional) properties
// opacity:0-100 // Lower number implies a more transparent dialog box
// zindex: # // HTML elements with a higher zindex appear on top of others with a lower number
// height: 0-100 // The height in percentage terms relative to the browser screen
// width: 0-100 // The width in percentage terms relative to the browser screen
// name: text // A name for your dialog box - this is what you will refer to when showing and hiding the dialog
// align: text // Should the text be placed in the: center, justify, left or right
// fontcolor: (#xxxxxx) // Standard RGB Hex color code
// fontweight: 100-900 // The boldness of the text in increments of 100. 900 implies maximum boldness
// bgcolor: (#xxxxxx) // Standard RGB Hex color code
// borderwidth: # // The thickness of the border
// bordercolor: (#xxxxxx)// The color of the border in HEX
// borderstyle: text // The style of the border. One of dashed, dotted, double, groove, inset, none, outset, ridge, solid
// modular_waiting(false); // hide the dialog
// modular_waiting(true, 'my message to show here', {'name':'myName', 'zindex':'50', 'bgcolor':'#0000FF', 'opacity':'70'}); // show the dialog
// Because options is JSON opacity/zindex/bgcolor are all optional and can appear
// in any order. Pass only the properties you need to set.
var options = options || {};
var zindex = options.zindex || 100;
var height = options.height || 25;
var width = options.width || 35;
var opacity = options.opacity || 90;
var opaque = (opacity / 100);
var bgcolor = options.bgcolor || '#FFDC75';
var fColor = options.fontcolor || '#0000FF';
var fWeight = options.fontweight || 600;
var bcolor = options.bordercolor || 'black';
var bstyle = options.borderstyle || 'solid'
var bwidth = options.borderweight || '3px'
var align = options.align || 'center';
var __div_id__ = options.name || '__modular__popup__';

var dark=document.getElementById(__div_id__ );

if (!dark) {
var tbody = document.getElementsByTagName("body")[0];
var tnode = document.createElement('div'); // Create the layer.
tnode.style.position='fixed'; // Position absolutely
tnode.style.overflow='auto';
tnode.style.display='none'; // Start out Hidden
tnode.id=__div_id__ ; // Name it so we can find it later
tbody.appendChild(tnode); // Add it to the web page
dark=document.getElementById(__div_id__ ); // Get the object.
}

if (vis) {
var txt = document.createElement('font')
txt.setAttribute('color',fColor);
txt.style.fontWeight = fWeight;
txt.innerHTML = msg.replace(/\"/g,""").replace(/\'/g,"'").replace(/\/g,">").replace(/\n/g,"<br/>")

dark.style.border = bwidth + " " + bstyle + " " + bcolor;
dark.setAttribute('align',align);
dark.style.opacity=opaque;
dark.style.MozOpacity=opaque;
dark.style.filter='alpha(opacity='+opacity+')';
dark.style.zIndex=zindex;
dark.style.backgroundColor=bgcolor;
dark.style.width= width+'%';
dark.style.height= height+ '%';
dark.style.display='block';
dark.style.cursor = 'wait';
dark.appendChild(txt);
dark.style.left = '40%'
dark.style.top = '50%'
} else {
// TODO - should i remove the element?
dark.style.display='none';
dark.innerHTML = "";
}
}

Activate/Deactivate with a function call to modular_waiting(vis, msg, options).

vis is a true/false variable. If you pass true, the dialog will appear. If you pass false, the dialog will be removed.

"msg" is the message that you would like to display.

options is an optional JSON object this allows you to send only the properties you want modified. You don't have to pass options if you don't want. modular_waiting(true,"Hello world") and modular_waiting(false) will work quite nicely.

To set all of the options, do the following {'opacity':'70', 'bgcolor':'#0000FF', 'zindex':'25', 'height':'50','height':'50', 'name':'myPopUpDialog', 'align':'left', 'fontcolor':'red', 'fontweight':'900', 'bgcolor':'yellow', 'borderwidth':'5', 'bordercolor':'blue', 'borderstyle':'dotted' }. This will create a somewhat transparent dialog box that covers approximately 50% of the window, with text aligned left, colored red and bold on a yellow background and a dotted, blue border!

Feel free to change the values yourself!

Order doesn't matter, mix and match however you wish.


Tuesday, June 05, 2007

The Dogs of the Dow - Half Year Performance 2007

Previously, I talked about the dogs of the dow and how you could use that as a strategy. Below is an update for those that are interested in this investment strategy. The table includes the Dow component and it's year to date increase.

Please note that dividends are not re-invested, but are included in the rate of return.

As of June 4, 2007:

Pfizer5.8%
Verizon Communications15.9%
Altria-16.1%
AT&T19.2%
Citigroup0.0%
Merck18.5%
General Motors4.0%
General Electric0.3%
Dupont9.3%
Coca-Cola9.1%

The overall rate of return for the first 6 months was about 6.6%.
The overall gain for the Dow in the same period was roughly 9.3%.

There were 2 components in negative territory (Citigroup was slightly in the red and Altria was in the red by double digits).

Cheers!

Disclaimer:
  • I am not be liable for any investment decision made or action taken based upon the information on this page.
  • I suggest you check with a broker or financial adviser before making any stock investing decisions.
  • I am not offering financial advise, but rather commenting on widely available investment strategies.
  • Finally, Caveat emptor!

Thursday, May 03, 2007

Gold Stocks - Trade Idea Part 2

Last time, I talked about naked put selling on gold stocks that you wouldn't mind owning. Let's assume, following the earlier example, that at the end of May you are 'put' into Barrick Gold Corp because the stock traded lower than the strike price of the put options you wrote (i.e. $30 USD).

This means that you are now the proud owner of 500 shares of ABX. What can you do? You could hold the stock and hope it goes up or you could sell the stock for a slight loss (remember, it must be less than $30 because you were 'put' into the stock). Alternatively, you could write covered calls on the stock.

So, what is a 'call'? A call option is an agreement that gives an investor the right (but not the obligation) to buy a stock (or some other other financial instrument) at a specified price within a specific time period. If you sell someone the right to buy stock that you own, then this is a covered call.

Imagine now that you sell 5 call contracts that expire in one months' time at the $30 strike price. Imagine that each contract is trading at $0.75. This would mean that you would receive:
  • $0.75 x 500 or $375 for selling someone the right to buy your stock in one months' time!
If at the end of next month, the stock is trading below the strike price of $30, then you keep your stock and the $375 that you made on the call options. For argument's sake, if the stock is trading at $50, you are obligated to sell your stock for $30. In other words, if on the day of expiration for your call option, ABX is trading at any price over $30, you must deliver your stock for $30.

Let's assume now that for one year, you write naked put options for ABX, and when you are put into the stock you write covered calls. Moreover, when you called to deliver your stock, you write naked puts for the following month. In other words, every month that goes by, you either own ABX and sell covered calls, or don't own ABX and sell naked puts.

Let's also assume that your monthly premium on the option writing is approximately $0.85. Then each month, you generate about 500 x $0.85 or $425. For the year, you would generate about $5,100. Remembering that you had to initially have $15,000 in your account to cover the naked put options, you are looking at yearly rate of return of about 33%. Not too shabby!

Disclaimer:
  • I am not be liable for any investment decision made or action taken based upon the information on this page.
  • I suggest you check with a broker or financial adviser before making any stock investing decisions.
  • I am not offering financial advise, but rather commenting on widely available investment strategies.
  • Options trading can be extremely risky.
  • Finally, Caveat emptor!

Tuesday, April 17, 2007

Gold Stocks - Trade Idea

Over the last year, I have benefited from one trading strategy involving gold stocks. First of all, I believe that gold will continue to rise, first as a hedge against inflation and second as a hedge against the falling us dollar.

With that said, it seems that gold stocks would be the place to be. My strategy is simple: write 'in the money' naked put options on gold stocks that I wouldn't mind owning for the amount of shares that I am willing to buy.

The seller of a put option has the obligation to buy shares in a company at a specified price at a certain time. In return for this obligation, a put seller gains a premium. Most gold companies are volatile, thus garnering larger premiums.

As an example, take Barrick Gold Corp (ABX on the NYSE; ABX on the TSX). Currently, this stock is trading at around $30 USD per share. Assuming that you thought this stock was going to follow the price of gold higher and that you wanted to buy 500 shares, then following the strategy above, you would:
  • sell 5 put contracts (uncovered) for the month of May for a premium of around $1 per contract
  • ensure that your account has enough money in it to finance the trade (in this example, $15,000)
Doing this, you would gain $500 (minus commissions) and come the third Friday of May, if the stock is trading less than $30, you are obliged to purchase 500 shares at $30 dollars. Otherwise, you keep the $500 and repeat the strategy the next month if you still are bullish on Barrick and the price of Gold.

Keep in mind that there are numerous gold producing companies out there, not just Barrick Gold and that those companies that exhibit wild fluctuations in daily share price, usually garner larger premiums.

Good luck.

Disclaimer:
  • I am not be liable for any investment decision made or action taken based upon the information on this page.
  • I suggest you check with a broker or financial adviser before making any stock investing decisions.
  • I am not offering financial advise, but rather commenting on widely available investment strategies.
  • Finally, Caveat emptor!

Friday, March 02, 2007

Solving Pesky ssh Issues in Cygwin

Every time that I re-install cygwin and I use ssh for the first time, I encounter minor annoyances. My biggest annoyance is the fact that no matter how many times I log into a remote machine, cygwins' ssh utility tells me the following:

Could not create directory '/home/ME/.ssh'.
The authenticity of host 'xxx.yyy.zzz.ca (1xx.82.67.xx)' can't be established.
RSA key fingerprint is 6c:59:15:64:ed:c8:67:35:d6:ed:1c:a2:ee:87:2b:3f.
Are you sure you want to continue connecting (yes/no)?
Once I enter 'yes', I get the following:

Failed to add the host to the list of known hosts (/home/ME/.ssh/known_hosts).

So that message occurs every single time that I attempt to login to the remote machine.

So the question is, how do I solve this problem? The solution was actually quite simple!

First locate the file called 'passwd' in your C:\path\to\cygwin\etc directory and open it with wordpad.

Second, replace the text
/home/YOUR_NAME
with
/cygdrive/c/Documents and Settings/YOUR_NAME

Finally, save the file.

In hindsight, the solution is very straightforward, but it took me hours to finally figure out. Hopefully, I can save your time!

Saturday, February 03, 2007

Maven, Windows and Deploying to a Remote Location

If you are a programmer on Windows, some tasks take longer to perform than others. Attempting to set up maven so that you can deploy files to a remote machine is one of those tasks!

Here is what you need to get the job done:
  1. Download plink
  2. Download puttygen
  3. Download pscp
If the above links do not work, use Google and search for "putty download".

Once you download them, place them in a folder called putty and move that folder to your root drive (C:\). The next thing that you should do is add that folder to the PATH. Not sure how to do that?
  1. press the windows key and 'pause break' at the same time.
  2. A system properties panel will appear. Click on the 'Advanced' tab.
  3. Click on the 'Environment Variables' button.
  4. Locate the PATH variable from the System variables portion of the panel and choose 'Edit'.
  5. Append, without quotations, ';c:\putty;' to the end of the PATH value.
  6. close the panels and you are done!

Now you need to create a private key from the remote server that you would like to deploy your files to.
  1. ssh into the remote machine
  2. execute the following command on the remote machine
    ssh-keygen -b 1024 -f ssh_host_key -N '' -t rsa
    This will create a file called ssh_host_key.
  3. On your local machine, create a folder called c:\ssh and place the file, ssh_host_key, in that folder from the remote machine.

The key that we generated on the remote machine will now be used to create one that is compatible with putty. We will now use puttygen to create a 'putty' private key.
  1. Double click the file puttygen.
  2. Make sure that you select you select 'ssh-2 RSA' at the bottom of the page.
  3. Next choose, Conversion -> import.
  4. Select c:\ssh\ssh_host_key.
  5. Save the generated key (save both the public/private key) in c:\ssh.

Most of the 'hard' stuff is now done. Next we will add our remote server to our local maven configuration. To do this, we will have to modify the file settings.xml.

The settings.xml file is usually located in
c:\Documents and Settings\<user>\.m2.

Before proceeding, you should back up the file. Open settings.xml in Wordpad and add the following fragment of xml into the file:

<server>
<id>remote-repository-id</id>
<username>your_username_here</username>
<password>your password here</password>
<privateKey>c:/ssh/your_private_key.pkk</privateKey>
<configuration>
<sshExecutable>plink</sshExecutable>
<scpExecutable>pscp</scpExecutable>
</configuration>
</server>
That piece of xml goes in the servers portion of the file. Add it and modify the bold pieces of the text.

That's really all there is to do. However, if you are lazy, like myself, then create a windows batch file that does the deploying for you!

Below is one that I use. You will have to modify the lines in bold. Run it without arguments first, to have it display the usage!

@echo off
if "%1"=="" GOTO ERROR
if "%2"=="" GOTO ERROR
GOTO DEPLOY

:ERROR
@echo off
echo There was an error deploying file.
echo usage:
echo %0 jar pom [repository_id]
goto END

:DEPLOY
setlocal
set REPO=%3
if "%3"=="" set REPO=remote-repository-id
@echo on
REM should be on one line!
mvn deploy:deploy-file -Dfile=%1 -DpomFile=%2 -Durl=scpexe://remote_server_domain/:/remote/path/to/maven/ -DrepositoryId=%REPO%
REM end one line
endlocal
@echo off
GOTO END

:END
@echo off