Thursday, July 24, 2008

Managing DataBound DropDownLists Properly

So I found a tricky problem with databound DropDownLists that causes the selectedIndexChanged event to fire more than you probably intended.

Normally when you add one - you might DataBind it, and then do something like

my_ddl.Items.Add(new ListItem("Select . . . "));

This can become tricky pretty fast if your DropDownList does anything major - like add a selection to a form. The problem is that dropdownlists seem to be loaded with a SelectedIndex of -1. This means that when they are unbound (aka have no data at all), they don't want to put 0 (because that would indicate an item) - so its a -1.

So you bind your data, and then add a top level ListItem for convenience - and now the selected Index is 0 - so SelectedIndexChanged event fires!

The simple fix is to force all of your DDL's to initialize to 0 - and always have a top item! That way, if a user selects the top item, the event does not fire, but any other databound item will work fine.

So, in Page_load, do this

protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
return;

my_ddl.SelectedIndex = 0;
}

Note that this only needs to be done for databound controls / controls created programmatically. I have not extensively tested, so I am not 100% on what controls this affects, but I am pretty sure that ones created declaratively in the ASPX page will not have this problem

Hamy

Monday, July 21, 2008

Blogger's Shortcomings

To say the least, I am pretty disappointed in blogger so far.

I can not add more tags - so if I post a 4 page tutorial (like the post just before this one) - my homepage is destroyed, rather than just showing a paragraph of the post and then a link to read the rest if you are interested.

The back end editor is quite sad. I try to add code a lot. There is almost no easy way to get code to format correctly. <pre> tags don't seem to always work, and they have no convenient <code> tag.

There is no way to add files. There is no nice upload interface or such. Come on - it's Google. Can't they even register your blog an email address and use that room as storage?


Ill be switching back to wordpress as soon as I get a chance.

~unhappy
hamy


PS - I do like the way blogger handles comments. Thats it.

ViewState - A Quick Users Guide

There are a lot of tutorials on ViewState. I have read quite a few of them, and this is meant to expand upon them by condensing information in an easy to understand format. Hope it helps.

GamePlan
I want to show you what ViewState can be used to store - with little to zero work on your part. I will start with the really simple stuff - then step it up a few times to finally end with Composite Controls (a custom control you make from other controls) with constructors.

No Dynamics - All Static Content
  • The first thing you will be told about viewstate is that declarative syntax is automatically stored. Example :

--------------------------------
ASPX
<asp:dropdownlist id="ddl" runat="server" enableviewstate="true">
<asp:listitem text="1" value="1"></asp:listitem>
<asp:listitem text="2" value="2"></asp:listitem>
<asp:listitem text="3" value="3"></asp:listitem>
<asp:listitem text="4" value="4"></asp:listitem></asp:dropdownlist>
--------------------------------


This DDL will always be visible on the page. If you choose an item, and cause a postback - your item will still be selected. This is the first thing everyone see's - which leads a lot of people to get the illusion ViewState is 'magical'. This isn't true, it's easy to understand. I am not covering how it works right now, just how to use it.

Programmatically Added Controls
  • Here it gets a tad more interesting. I am programmatically adding a DDL to the page. The key is - if you add something after the page has been sent to the client once (this is a tad misleading, but its the really simple explanation) - then you must re-add it to the page EVERY time


--------------------------------
ASPX
<asp:panel id="panel" runat="server" enableviewstate="true">
</asp:panel>

Code Behind
protected void Page_Load(object sender, EventArgs e)
{
add_ddl();
}

private void add_ddl()
{
DropDownList d = new DropDownList();
panel.Controls.Add(d);

if (d.Items.Count == 0)
{
d.Items.Add("First");
d.Items.Add("Second");
d.Items.Add("Third");
d.EnableViewState = true;
}
}
--------------------------------
  • So - the nifty part is that via some .NET naming stuff and fancy tricks - Your settings will be restored - even though it is technically a 'new' control! What this means is that you can programmatically add two controls, change their settings, and cause a postback - and re-add new controls in the same place - and they will still have the settings you chose before!

Composite Control Without Special Constructor
So - this will show you how to build a simple composite control. The nice thing is that this example is really the exact same as the last one - just with a composite control. All you have to do is (assuming you add your composite control at run-time, aka in the Code Behind and not in the aspx) re-add your control and any values you choose before will be automatically persisted. Pretty neat.

--------------------------------
Code Behind
{ . . .

protected void Page_Load(object sender, EventArgs e)
{
add_ddl();

panel.Controls.Add(new Add_Composite_Control());
}

. . . }

public class Add_Composite_Control:CompositeControl
{
private DropDownList dl;

protected override void CreateChildControls()
{
Controls.Clear();
CreateControlHierarchy();
ClearChildViewState();
}

protected virtual void CreateControlHierarchy()
{
dl = new DropDownList();

dl.Items.Add("Custom 1");
dl.Items.Add("Custom 2");
dl.Items.Add("Custom 3");

Controls.Add(dl);
}
}
--------------------------------
  • Once again, the nifty thing here is that .NET naming classes can "remember" the right names for everything, so your changes persist across postbacks. So - try it out. Add two of these controls to the page, and vary their values. Cause a Postback - and the controls will be re-created with the values they had before.


Composite Control With Special Constructor
  • This is the hardest example I am going to show you. It involves manually setting stuff in the ViewState. Up until now, we have just had to re-add controls and everything has been quite easy.
  • We need to set ViewState variables because we need to re-add the controls to the page. And without the constructor arguments, we cannot re-add anything. Note that we do not have to remember what value was selected, or anything like that. We are still using ViewState to it's full advantage. All we need to be able to do is re-add the controls
--------------------------------
ASPX
<asp:Panel ID="panel1" runat="server">
</asp:Panel>

<asp:Button Text="Add Control" ID="btn2" runat="server" OnClick="Add_Control" />


Code Behind

{ . . .

protected void Page_Load(object sender, EventArgs e)
{

if (ViewState["storage"] != null)
{
// Create the regex
string variables1 = (string)ViewState["storage"];
Regex reg = new Regex(@"\|(?.+?),(?.+?);");
Match m = reg.Match(variables1);

// extract the variables
while (m.Success)
{
string varname = m.Groups["name"].ToString();
string id = m.Groups["id"].ToString();
panel1.Controls.Add(new Log_Format_Variable(varname, id));
m = m.NextMatch();
}
}
}
protected void Add_Control(object obj, EventArgs e)
{
Random r = new Random();
int rand = r.Next(1,3);

if (rand == 1)
{
panel1.Controls.Add(new Log_Format_Variable("var_one", "1"));
ViewState["storage"] += "|var_one,1;";
}
if (rand == 2)
{
panel1.Controls.Add(new Log_Format_Variable("var_two", "2"));
ViewState["storage"] += "|var_two,2;";
}
if (rand == 3)
{
panel1.Controls.Add(new Log_Format_Variable("var_three", "3"));
ViewState["storage"] += "|var_three,3;";
}
}

. . . }

public class Log_Format_Variable : CompositeControl
{
private DropDownList d;
private string var_ID;
private string varname;

public Log_Format_Variable(string varname, string var_ID)
{
this.var_ID = var_ID;
this.varname = varname;
}

protected override void CreateChildControls()
{
Controls.Clear();
CreateControlHierarchy();
ClearChildViewState();
}
protected virtual void CreateControlHierarchy()
{
Label l = new Label();
l.Text = "Function for " + varname;

LiteralControl lc1 = new LiteralControl(" ");

d = new DropDownList();
d.Items.Add(new ListItem("Sum", "SUM"));
d.Items.Add(new ListItem("Average", "AVG"));
d.Items.Add(new ListItem("Max", "MAX"));
d.Items.Add(new ListItem("Min", "MIN"));

LiteralControl lc2 = new LiteralControl("
");

this.Controls.Add(l);
this.Controls.Add(lc1);
this.Controls.Add(d);
this.Controls.Add(lc2);
}
}

So, basically all I am doing extra here is saving the information needed to re-call the constructors. I use a small schema to save the constructor info - the "|varname,var_id;" scheme makes it simple for me to get the info back out of the ViewState, but this is really not necessary - there are many better ways to store the info. The Add_Control just lets me add a few random comtrols, and it does not matter if two controls have the same constructor information. Every CompositeControl implements a Naming interface, which means that all things belonging to that control have a unique prefix. Hence, if two controls have the same constructor info - they are still regarded as two separate entities. So - try this out. You can create as many of these as you would like, and they will all be able to remember their values across subsequent postbacks.

WrapUp
Here is the source code for the entire project - which includes all of the control types listed above.


Things to note
  • This probably did not help you understand at all why viewstate works. Please read up on that to get the full picture
  • To cause a postback, just add a button to your ASPX page. Don't assign it an OnClick value, and it will simply postback and do nothing else
  • ViewState information is saved on the CLIENT side - and can thus be victim to a few things. Notably, someone can tamper with it. It is weakly encrypted and can be easily changed.
  • Again about the client side - this means larger pages, larger page load times, etc. Long story short, don't store a lot of information in the ViewState.
  • ViewState is enabled by default. All of my enableviewstate="true" are just for clarity. You don't need them

Please let me know if I have goofed anything or could have done something better. This is not meant to be an in-depth guide to understanding ViewState, but rather a quick-flip guide to using it easily.

Hamy

References

Understanding ASP.NET ViewState by Scott Mitchell
Control Building and ViewState Lesson for the Day
Binary Fortress ViewState Helper Tool
There is at least one significant other site that I sadly cannot remember the name of. There are also a significant number of small helper sites.

When Databinding results in System.Byte[]

So - quite a few people seem to be having an issue with DataBinding to any sort of control (Dropdownlist, DataGrid, etc) resulting in the bound control printing out System.Byte[]. This may not be the one true answer, but it is definitely one of them.

I noticed a lot of people (like myself) were having trouble with MySql - specifically connector/net. The solution is simple, and probably affects other database storage engines just as well.

Basically - the just of it was this - here is my code to bind a DropDownList. Pretty readable stuff

----------------------------
string sql = "CALL Get_LFID_info('" + ddl_Log_Formats_1.SelectedValue + "');";
DataTable dts = ExecuteMySqlAdapter(sql);
relation1.DataSource = dts;
relation1.DataTextField = "extended_varname";
relation1.DataValueField = "variable_id";
relation1.DataBind();
----------------------------


The issue with this case was that my procedure calculated the result of "extended_varname" - which means that it was created by doing a SELECT CONCAT(name,".",id) sort of deal. Which meant there was no column type associated with it. The solution - pretty simple. Just cast the result. I did this - SELECT CAST(CONCAT(name,".",id) AS CHAR) AS extended_varname . While I believe that you might be able to so that simpler, it was the easy answer. If I figure out a better way to cast it, I will let you know - but the point is the general idea that the column needs to have a specific type associated with it before MySql Connector can correctly convert the rows to strings - any System.Byte[] result generally indicates a cast went wrong somewhere.

Hope that helps
-Hamy

References
mysql type conversion

Wednesday, July 2, 2008

Bypass Security so you can play Flash videos and Games

So, if you have ever been at a work/school/restricted computer and cannot play Flash games or videos, good news! There is an easy workaround even if the installers will not work.

Step 1 - install Firefox
  • get the file from http://www.mozilla.com/en-US/firefox/
  • Run the installer, and choose Custom installation
  • You want to install it inside the My Documents folder
  • Preferably, create a folder inside your My Documents called FireFox and install it there
Step 2 - download the Flash plugin
  • Right click and choose 'Save as' on the following link
  • http://fpdownload.macromedia.com/get/flashplayer/xpi/current/flashplayer-win.xpi
  • Rename the file to flashplayer-win.zip
  • If you get a warning, ignore
step 3 - install the plugin manually
  • open the firefox folder plugins folder
  • probably located somewhere like this:
  • C:\Documents and Settings\My_User_Name\My Documents\FireFox\plugins
  • Open the flashplayer-win.zip and copy two files into your plugins directory
  • You will need to copy over NPSWF32.dll (required) and NPSWF32_FlashUtil.exe (optional, just help files)
Step 4 - restart flash and start browsing!

Hope that helps someone. A little reasoning behind what we did:
We installed into the my documents folder so that no matter what user you are, you have full permissions to write, execute, and modify files (basically gets rid of that annoying "you need to be an administrator to do this" dialog)
*.xpi files are the same thing as *.zip files, just with a different extension. They can be renamed easily and that gives easy access to all the files we need

Hamy

How do you know when to take a break from Developing?

So, how do you know when to take a break from developing? Here's how:

User 'hamy' has exceeded the 'max_connections_per_hour' resource (current value: 9999)


(If you don't know - this means that in one hour I connected to mysql over 9999 times)

Obviously, I am working on a DB intensive program - Its point is to enable logging, unit testing, and metric graphing that can be setup in a matter of fifteen minutes, easily changes and thrown away.

:)

hamy

Tuesday, June 17, 2008

How to view stuff on linux

So, I have been learning a butt load about linux lately. This means I have hardly scratched the ice berg, but just in case someone finds themselves as lost as I was at first, here is a quick list of commands to view files on Linux.

If you open a terminal, and navigate around a little (if you don't know how to navigate, try Googleing change directory) eventually you find a file that you want to open and read/edit

Sometimes this read/edit stuff can be hard, so here are a few things to try:

vim filename
gedit filename
emacs filename
edit filename
less filename
more filename

If all else fails, you can do cat filename, which just prints out the contents to the terminal, no editing allowed.

To exit out of those text editors can also be challenging. Try Esc, or hit : then q. The : tells it you want to issue a command, and q is for quit(or quit/save). I am sure there are others, like quit and no save, but you will have to look those up on your own.

hamy

Monday, June 9, 2008

Getting aircrack to work on Dell Latitude D620 using Ubuntu

Update - I have gotten this working and am working on writing a basic intro to how I did it. Before I do that, however, I would like to basically try it from scratch again to make sure I can write a decent tutorial.

So, I am trying to get aircrack installed on Ubuntu. I have never been able to get a wireless card to capture in 'promiscuous ' mode, meaning (as I understand it) that it not only captures the packets to/from you, but also all the packets to/from everyone else that are in range of your card.

So, figured I would give it a shot. So far its a spotty road.
Started with this nice tutorial
http://www.aircrack-ng.org/doku.php?id=getting_started

Sadly, my card was not listed ;0 . Following the recommendation, I Google Linux, which brings me to this lovely article
http://intellinuxwireless.org/?
Note: The iwlwifi project provides a driver which utilizes the mac80211 subsystem for the Intel® Wireless WiFi Link 4965AGN and Intel® PRO/Wireless 3945ABG Network Connection adapters.

So, I get busy trying to install the mac80211 stuff. I go through a lot of stuff, but the final result is not so good. One thing to note that is not on their page about install the subsystem, got off zolton's blog was this line
sudo ln -s /usr/src/linux-headers-`uname -r` /lib/modules/`uname -r`/source

Otherwise your compilation fails b/c it cannot find anything in the /source folder.

So, I managed to make, and then make_install. Sadly, I got weird errors make modules module_install stage on the how to install mac80211 subsystem page

turns out that with ubuntu 8.04 there is a bug in the kernal, and its not exactly a breeze to get it working till that is fixed.

So, I twiddle my thumbs for a bit and head on over to the compat-wireless project. So far, its going well. I have DLed the tar, ungipped, and we are compiling ;)

Post updates if I get it working.

Sunday, June 8, 2008

Weird C Sharp Errors I ran into today

So, I just spent a while trying to fix these CS errors:

c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\cuts_try_3\9354e1fa\4cc0037f\App_Web_drt6meif.2.cs(237,29): error CS0115: 'ASP.savedcharts_aspx.GetTypeHashCode()': no suitable method found to override
c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\cuts_try_3\9354e1fa\4cc0037f\App_Web_drt6meif.2.cs(242,30): error CS0115: 'ASP.savedcharts_aspx.ProcessRequest(System.Web.HttpContext)': no suitable method found to override
c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\cuts_try_3\9354e1fa\4cc0037f\App_Web_drt6meif.2.cs(128,18): error CS0535: 'ASP.savedcharts_aspx' does not implement interface member 'System.Web.IHttpHandler.IsReusable'

After a reasonable amount of time, I realized one of my files is named SavedCharts.aspx

So, whats the problem? When you choose place code in a separate file (aka code behind), .NET auto generates that file for you. Supercool. The problem comes when you rename the newly created file (like renaming default.aspx to, lets say, savedcharts.aspx). Sometimes, the class name in the code behind file does not get changed.

Here is a quick example that would throw these errors:

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="CreateNewUT.aspx.cs" <span style="color: rgb(51, 204, 0);">Inherits="CreateNewUT"</span> Title="Untitled Page" %>
<-- insert asp/html page here -->

public partial class<span style="color: rgb(255, 153, 0);"> Default :</span> System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}
}


The problem is, I changed my page, and told it to inherit from class CreateNewUT. But, I didnt rename the class in my code behind page. To Fix:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="CreateNewUT.aspx.cs" <span style="color: rgb(51, 204, 0);">Inherits="CreateNewUT"</span> Title="Untitled Page" %>
<-- insert asp/html page here -->

public partial class<span style="color: rgb(255, 153, 0);"> <span style="color: rgb(102, 255, 153);">CreateNewUT :</span></span> System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}
}


Hope that helps someone ;) Don't know why VS and/or VWD 2008 seem to correctly rename the class some and not some, but hopefully that gets ironed out soon.

Hamy

Friday, June 6, 2008

FTP Backup

So, today I started using Fling, a nifty little freeware FTP backup utility that lets me create a folder (call it BackedUp for me) and put whatever I want in there to be backed up to my server. Pretty cool huh?

All in all, great simple product. I would recommend, and i found a good source for freeware in the meantime ( http://www.nch.com.au/ ), if you don't mind putting up with some bad advertising its good software

hamy

Things I like:
  • it notices when files change, and auto backs them up again
  • nifty icon lets you know its running - not gaudy. Quite a pleasing icon ha ha
  • it keeps a running log so you can open the window and see what it is currently doing
  • they put X's or checks on files to let you know whats backed up(currently, about 7 or 8 Gigs of pictures have little a 'x' :) ).

Things I don't like:
  • The way the company advertises. I started out looking at another product of theirs but gave up and uninstalled after it added like 5 things to my start menu. (Yep, i scanned it. Its not a virus/spyware, just crappy advertising) However, in defense Fling has been pretty good. If you click the toolbox icon, it auto connects to the net and DL's another installer for another product :( LAME ZoneAlarm caught it, but another instance of bad advertising
  • the fact that you have to put stuff in the BackedUp directory. No multiple directory support, no custom file extensions. They have that with bigger tools, but none of those do the auto backup (although, again in their defense, there is a right click and fling upload menu that I have ignored. I secretly like the BackedUp folder - it keeps me deleting things I don't need)
  • Its not the fastest. I feel like they purposefully slow the upload a tad to preserve your normal Bandwidth

Wednesday, June 4, 2008

iTunes upgrades! Full Lyrics, Album art, ordered Music

So - iTunes has some problems. If you follow along, I will show you a few nifty tricks/programs to do these things:

1 - get lyrics to almost every song you have on your ipod and in your itunes
2 - do the same for album art work
3 - remove those pesky (!) missing files from itunes
4 - find/add the files that you didnt know you had
5 - fix all the ID3 info tags - remove those www.musicblah.blah.whatever links

There are a few things that I can’t do yet - so hopefully more soon. Here is what I can’t do for you:

1 - delete duplicates easily aka not one by one
2 - do ALL the work for you. You still have to put in fifteen minutes. I did all this, and I still have about 100 problem tracks. I will fix them one by one, because I am that guy. Most people probably don’t care. The point is, this stuff does 95% of the work, you still have to help it that 5% along.
3 - i am sure there is another one ;)

So - I did a few simple things to my itunes, mainly:

1 - fix all the ID3 tags (a LOT better than itunes automatic service can)
2 - remove the bad stuff (missing files, ect)
3 - get the lyrics
4 - get the artwork
5 - sync!

To see the rest of this - You will need to click read the rest link right below

Starting with 1 - fix all the ID3 tags
Flat out, this is the hardest and/or longest step. Once this is done, its just one click and waiting on your part, so stick the first part out!
1) download a nice program called MusicBrainz http://musicbrainz.org/.
2) install, open
3) add folder, and select your My Music folder (or main music folder - it will get subdirectories auto)
4) once it is done processing, click cluster
5) both of those steps take a bit. Once that is done, select all unclustered files (to save time here, try to only select the files that you know are songs. Aka dont select your recorded lectures - you are just wasting time) and click scan
Note: basically it is ‘listening’ to every file, and trying to match it against a database of files. This is really nice, it can turn ‘track 15′ into ‘Crash Into Me’ by …

6) Select all the albums that popped up on the right (click one, and hit ctrl + A) and click the big ’save’ button at the top.
Note: Scroll to the top of the right album list before you click save, and you can see the *’s going away. AKA they indicate unsaved albums.

7) Open itunes, if its not open, and select all songs (click to select one, and hit ctrl + A). right click, and choose get info. Ignore the warning, and hit ok — NOTE: DO NOT HIT ANYTHING BUT OK. YOU WILL WIPE ALL DATA FROM YOUR SONGS OTHERWISE

8) this is the end of the auto tutorial on this part, but FYI this is a really powerful program. You have to put some effort in to understand how to really work it, but if you are a fanatic this program is very valuable.

Now on to 2 - remove the bad stuff (missing files, ect)
1) Download and install ITLU http://itlu.ownz.ch/wordpress/?page_id=5. Note: he recommends backing up the library, which I did, but I have had no problems with the program

2) open the beast
3) click the blank square next to Location, browse to your My Music directory (or other main music place) and click ok to add it.
4) click every single box
Note: it is smart to actually repeat this step twice. (well, from 2 ;) ) The first pass will remove all the files that are linked incorrectly, and the second one will add them back, linked correctly. In laymans terms, run it twice ;)
5) click start, and grab a snack

Pressing on …. 3 - get the lyrics
1) download/install iLyrics http://code.google.com/p/ilyrics/downloads/list
Note: this is in Beta - so you kind of have to sit there and watch it and click ok everytime something unexpected happens. THis is why we removed the bad stuff first - this program has a tendency to crash if a file is missing
Note2: lyrics come from lyricwiki.org - so yea, if you have a song and want lyrics - you can add them to LW and iLyrics will suddenly work for those songs

2) open itunes, and ilyrics
3) in itunes, click music, click on a song to select it, and hit ctrl + A to elect all songgs
4) in ilyrics, click both checkboxes
5) sit there and babysit while it runs. You will probably have to click through about 30 error messages (thats what I had to, with about 2500 songs in my itunes)

Sweet, so on to album art
this ones pretty simple, just choose all songs again in itunes, (select one, then hit ctrl + A) and choose advanced->get album artwork

Sync!!!
now plug in your ipod, and wait for the massive sync

Note: on an ipod touch, seeing the lyrics means playing a song and clicking once on the screen (its kind of hard to find, so fyi if you see the repeat symbol, you are in the right place. If you dont see lyrics, there are no lyrics on that song in itunes. To check - open itunes, find that song, right click and do get info, and check the lyrics tab)

Hope that helps some people! It took me a while to do all this, so I figured I would share some stuff ;) Also - please digg this baby if you liked it!

Hamy

Ha Ha....not much better

I am obviously not a degisner. Wish they had a site that would just let you import a Fireworks png and say make this work. Anyways, this will do for now, so I am going to steal my iTunes post and get back to work. Later, if I am feeling productive I can try to make a real site template.

hamy

Horrid Template

I think order 1 of business (besides failing at finding a good blogging add on for FF) is to change the horrible templates provided, or try to make one ;)

Depressing Start

So I just read thru about 200 FF addons to find one that would allow me to simply click a button in FF, type in a blog, and submit.

The best I have come up with is BlogThis!, which has a depressingly small text entry area in a popup window that cannot be resized. How seriously lame.

I liked the look of scribefire, but I keep getting errors. A comment on their homepage mentions a blogger bug, it seems like I have been a victim. I tried both the old and new api urls, and appended ?alt=rss to them as well.

Any idea how to make scribefire work, please let me know ;)

Side note, its really time to update to FF3. I was trying to hold true to the DL date, but now that Sage Too is coming along nicely to replace Sage, the only thing that was holding me back from upgrading is gone :)

cheers
Hamy

test


this is a quick test