Monday, December 13, 2010

Resouce files and how to start using them


Resource refactoring might be a better title, but I preferred a more bombastic one.
Recently I had to refactor a complete website in order to allow localization of the pages.
The major problem with those kind of things is finding the HTML texts which are not related to anything (i.e. it’s a text that will be parsed on the client side only)
I tried locating a tool for the job. For some unknown reason there ain’t one free, buying one was out of question due to the time we had (2 days). “Build one”, I guess it’s one of the most common answers a programmer (senior software engineer for the ones who really need the title) gets at work.
Build one? OK, I did.
Step one: use whatever is available inside the VS. So we grabbed this piece of Macro to use the refactoring made by the studio:
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE90a
Imports EnvDTE100
Imports System.Diagnostics
Imports System.Collections.Generic

Public Module Localization
    Dim _oProject As Project
 

    Sub GenerateLocalResources()
        Dim _oObject As System.Array = DTE.ActiveSolutionProjects()
        Dim _oProject = _oObject(0)
        GenerateLocalResourcesForProjectItems(_oProject.ProjectItems)
    End Sub

    Private Sub GenerateLocalResourcesForProjectItems(ByVal oProjectItems As ProjectItems)
        Dim oProjectItem As ProjectItem
        Dim fileName As String
        For Each oProjectItem In oProjectItems
            fileName = oProjectItem.Name.ToLower()
            If (fileName.EndsWith(".aspx") OrElse fileName.EndsWith(".ascx")) Then
                Dim window As Window = oProjectItem.Open(Constants.vsViewKindDesigner)
                window.Activate()
                Try
                    DTE.ExecuteCommand("Tools.GenerateLocalResource")
                Catch ex As Exception
                End Try
                If DTE.ActiveDocument().Name = fileName & ".cs" Then
                    DTE.ActiveDocument().Close(vsSaveChanges.vsSaveChangesNo)
                End If
                DTE.ActiveWindow.Close(vsSaveChanges.vsSaveChangesYes)
            End If
            If Not oProjectItem.ProjectItems Is Nothing AndAlso oProjectItem.ProjectItems.Count > 0 Then
                GenerateLocalResourcesForProjectItems(oProjectItem.ProjectItems)
            End If
        Next
    End Sub
End Module


As usual slight editing was required, but it does the job (on VS2010).
Note: it’s safer to exclude the web.config while running it. The “globalization provider” defined in it can be a pain in the ass.
Step two: run a “re-formatter” on the results. The re-formatter consists of a loop on all files (omitted from this article), read file’s resource file, get “extra” resources from the file itself and finally create a new resource file and new file.
Step two, first part: read (and write...) the resource file
private static System.Collections.SortedList ReadResourceFile(string resourceFilename)
{
    System.Resources.ResXResourceReader rr = new System.Resources.ResXResourceReader(resourceFilename);
    System.Collections.IDictionaryEnumerator ide = rr.GetEnumerator();
    System.Collections.SortedList tags = new System.Collections.SortedList();
    while (ide.MoveNext())
    {
        if ((ide.Key is string) && (ide.Value is string) && (((string)ide.Value).Length > 0))
        {
            tags.Add(ide.Key, ide.Value);
        }
    }
    return tags;
}

private static void WriteResourceFile(string filename)
{
    System.Resources.ResXResourceWriter rw = new System.Resources.ResXResourceWriter(filename);
    for (int i = 0; i < tags.Count; i++)
    {
        rw.AddResource((string)tags.GetKey(i), (string)tags.GetByIndex(i));
    }
    rw.Close();
}


Step two, second part:
 The first thing to do is “dispose of the garbage”, strip the HTML from all the tags etc. I created a function to do that:
private static System.Collections.ArrayList GenerateList(string Filename)
{
    System.Collections.ArrayList res = new System.Collections.ArrayList();
    System.IO.StreamReader sr = new System.IO.StreamReader(Filename);
    string aspx_text = sr.ReadToEnd();
    sr.Close();
    aspx_text = RemovePiecesOfJunk(aspx_text, "
<script", "/script>");
    aspx_text = RemovePiecesOfJunk(aspx_text, "
<%", "%>");
    aspx_text = RemovePiecesOfJunk(aspx_text, "
<!--", "-->");
    aspx_text = RemovePiecesOfJunk(aspx_text, "
<asp:RegularExpressionValidator", "</asp:RegularExpressionValidator>");
    aspx_text = aspx_text.Replace("
<br />", "\r\n");
    aspx_text = aspx_text.Replace("
<br/>", "\r\n");
    aspx_text = aspx_text.Replace("
<br>", "\r\n");
    aspx_text = aspx_text.Replace(" ", " ");
    while (aspx_text.Length > 0)
    {
        int opening = aspx_text.IndexOf('>');
        if (opening > -1)
        {
            aspx_text = aspx_text.Substring(opening + 1);
            int closing = aspx_text.IndexOf('
<');
            if (closing > -1)
            {
                string tmp = aspx_text.Substring(0, closing).Trim();
                aspx_text = aspx_text.Substring(closing + 1);
                if (!aspx_text.ToLower().StartsWith("/script>"))
                {
                    if ((tmp.Length > 0) && (!res.Contains(tmp)))
                    {
                        res.Add(tmp);
                    }
                }
            }
            else
            {
                aspx_text = "";
            }
        }
        else
        {
            aspx_text = "";
        }
    }
    return res;
}

And the additional piece:
private static string RemovePiecesOfJunk(string orig, string open, string close)
{
    open = open.ToLower();
    close = close.ToLower();
    while (orig.ToLower().IndexOf(open) > -1)
    {
        int opening = orig.ToLower().IndexOf(open);
        int closing = orig.ToLower().IndexOf(close);
        orig = orig.Substring(0, opening) + orig.Substring(closing + close.Length);
    }
    return orig;
}

 
Take a note: THIS IS NOT THE BEST METHOD FOR THE JOB! It’s just a quick ‘n dirty solution which hands the needful things in time.
I removed the JS code on purpose, I didn’t need to translate the things in it (luckily…) The regular expressions removal is due to the usage of “unexpected” openings and closings within it. The algorithm just assumes that text between “>” and “<” is a client-side only text.
Step three: do the same to the code behind files.
I did it by locating special “hooks” in the  file (i.e. .Text = “XXX”) and replacing them with a reference to the global resource file. The code looks like this (without the loop for getting the files…)

 private static string ReplaceSingleInstance(string orig_cs, string hook)
{
    string res = orig_cs;
    while (res.IndexOf(hook) > -1)
    {
        int strt = res.IndexOf(hook);
        int end = res.IndexOf('"', strt + hook.Length);
        res = ExtractPortion(res, strt + hook.Length, end);
    }
    return res;
}
private static void ChangeFile(string fl)
{
    string tmp_cs = System.IO.File.ReadAllText(fl);
    string[] hooks = { ".DescritpionAfterDropDown = \"", ".NotDropDownNegateText = \"", ".NotDropDownDefaultText = \"", ".DescritpionBeforeDropDown = \"", ".Text = \"", ".Description = \"", ".HeaderText = \"" };
    bool changed = false;
    foreach (string hk in hooks)
    {
        if (tmp_cs.IndexOf(hk) > -1)
        {
            changed = true;
            tmp_cs = ReplaceSingleInstance(tmp_cs, hk);
        }
    }
    if (changed)
    {
        System.IO.File.SetAttributes(fl, System.IO.FileAttributes.Normal);
        System.IO.File.WriteAllText(fl, tmp_cs);
        Console.WriteLine("Changed " + fl);
    }
}

Step four: QA!
There’s no way to avoid it. After all the automatic conversions some JS texts remained (I wasn't that lucky after all), a special function in order to avoid the ‘ (common in French) was needed, and last but certainly not least GUI.

Step five: GUI repair.
I’m not joking, just translating from English to French made some sentences freak out. Lines were broken in the mid, images moved out of the frame, and some buttons “disappeared”.

Sunday, October 31, 2010

Some rantings from a seasoned programmer

No code this time!

Lately I've had to deal with some optimization problems. Mostly related to large DB, and some from the communication areas. The first thing that comes to mind when you get a problem of those types is: "Use design pattern". Welp, NO-GO!

Apparently design patterns are great for the computer science department, but when it comes to real life there are always limitations which renders the design pattern useless.
The one I encounter most is compatibility. Either to an old component written in Delphi/VB6/VC that has no source code available, yet it's used company wide. Once there's no budget to replace it, than there's no time and the best is "will remove it in the next version". Yeah right, I'm in the field more than 10 years, I have never (and mark it in whatever way you'd like) seen an old component being thrown without intervention from a regulator.
Leaving a "TO -DO:" line in the code means "maybe in a decade or two somebody would be bored enough to do that". I know I did leave a few of those comments on some pieces of software due to deadlines set by an un-interested client.

It appears as if a programmer's alert about a future problem can go unnoticed for as long as the software doesn't blow. When it does blow, a must happen unless the business is going bankrupt, the usual treatment is "patch it here" and "give extra whatever" to the server.
Unfortunately all of the above treatments are "snake oils". It reminds me of an old UNIX joke: "What does a sysadmin do when the CPU reaches 100% load? Adds monitoring service that sends mails to the abusers..."

Don't get me wrong, I still love programming. I'm supposed to call myself "Software Engineer", I hate the term! As a programmer I do more than simply engineer it, I design it, I code it and for most of the day I debug it.
A software engineer means there's a software architect which does the designing. So far I've seen a few "design by architect" projects. None could be shipped to a client, all of them were perfectly sensible from a programming point of view. It appears that a "programmer" means you can talk to the client, engineer mustn't, and architect usually thinks the client is his own personal toilet and pisses on it (sorry for the language, being politically-correct is not on top of my list).

That's it for now, next, another debugging session with a bunch of "nice" bugs lurking around the code and showing their tentacles from time to time just when you think they're gone for good.

Sunday, September 12, 2010

XML comparison

Today I needed an important feature that for various reasons can’t be fully implemented, the comparison of 2 XMLs which vary slightly. I did similar things before, but every time there's a new twist...
Let me begin by defining the “vary slightly”. The XMLs are something like
Original:

<XML>
<T></T>
<T ID=”1”>
<props>
<prop key=”A1” value=”X”></prop>
<prop key=”B1” value=”Y”></prop>
</props>
</T>
<T></T>
<S ID=”A”></S>
</XML>

And new:

<XML>
<T ID=”1”>
<props>
<prop key=”A1” value=”X”></prop>
<prop key=”B1” value=”Y”></prop>
</T>
<S ID=”A”></S>
</XML>

Any “normal” comparison would jump claiming the number of nodes doesn’t match. While being 100% correct it’s utterly useless for me because those are empty nodes which the XSL parser removes on the next phase. So why not use the XSL as a validator? Well we could, but, we want to check the XSL too…
A quick googling showed me that there’s no ready from the box solution, though apparently many need it.
Here’s my small contribution to whomever may need it. It’s not perfect. I took the liberty to tweak it a bit to my needs (only the first attribute is relevant for the xpath.) On the plus sign, it’s thread-safe, as usual I need my things to work in multi-threaded environments…

public class XML_Comparer
{
private static object lock_obj = new object();
private static int CompareError = 0;

private System.Collections.ArrayList l_IgnoredAttribs = new System.Collections.ArrayList();
private string l_status = "";
private string status_log = "";

public System.Xml.XmlDocument OriginalDocument { get; set; }
public System.Xml.XmlDocument NewDocument { get; set; }
public System.Collections.ArrayList IgnoredAttribs
{
get
{
return l_IgnoredAttribs;
}
}
public string BasePath { get; set; }
public string Results { get; private set; }
public string Status
{
get
{
lock (this)
{
return l_status;
}
}
private set
{
lock (this)
{
l_status = value;
status_log += value + "\r\n";
}
}
}

private void CompareXMLs()
{
Status = "Comparisson started";
string res = CompareXMLNodes(NewDocument, OriginalDocument);
if (res != "")
{
lock (lock_obj)
{
CompareError++;
NewDocument.Save(BasePath + CompareError.ToString() + "_new.xml");
OriginalDocument.Save(BasePath + CompareError.ToString() + "_old.xml");
Results = BasePath + CompareError.ToString() + "\r\n" + res + "\r\n" + Results;
}
}
Status = "Comparisson complete";
}
private string CompareXMLNodes(System.Xml.XmlNode new_nd, System.Xml.XmlNode orig_nd)
{
if ((new_nd == null) || (orig_nd == null))
{
Status = "Incorrect comparison!";
throw new Exception("Incorrect comparison!");
}
Status = "Comparing " + orig_nd.Name;
StringBuilder res = new StringBuilder();
if (orig_nd.Attributes != null)
{
System.Xml.XmlAttribute atr = null;
for (int i = 0; i < orig_nd.Attributes.Count; i++)
{
if (!l_IgnoredAttribs.Contains(orig_nd.Attributes[i].Name))
{
if (new_nd.Attributes != null)
atr = new_nd.Attributes[orig_nd.Attributes[i].Name];
if (atr == null)
{
res.AppendLine("Missing attribute: " + orig_nd.Attributes[i].Name);
}
else if (orig_nd.Attributes[i].InnerText != atr.InnerText)
{
res.AppendLine("Attribute " + orig_nd.Attributes[i].Name + " - values don't match (" + orig_nd.Attributes[i].InnerText + "!=" + atr.InnerText + ")");
}
}
}
}
if (orig_nd.ChildNodes.Count == 0)
{
if (orig_nd.InnerText != new_nd.InnerText)
{
res.Append(orig_nd.InnerText + "!=" + new_nd.InnerText);
}
}
else
{
foreach (System.Xml.XmlNode nd in orig_nd.ChildNodes)
{
string path = nd.Name;
if (nd.Attributes.Count > 0)
{
path += "[";
{
path += "@" + nd.Attributes[0].Name + "='" + nd.Attributes[0].InnerText + "'";
}
path += "]";
}
System.Xml.XmlNode new_child_nd = new_nd.SelectSingleNode(path);
if (new_child_nd != null)
{
string nd_res = CompareXMLNodes(new_child_nd, nd);
if (nd_res != "")
res.AppendLine(nd_res);
}
}
}
if (res.Length > 0)
{
return "Node " + orig_nd.Name + "\r\n" + res.ToString();
}
else
{
return "";
}
}

public XML_Comparer()
{
//
}
public XML_Comparer(string orig_xml, string new_xml)
{
OriginalDocument = new System.Xml.XmlDocument();
OriginalDocument.LoadXml(orig_xml);
NewDocument = new System.Xml.XmlDocument();
NewDocument.LoadXml(orig_xml);
}

public void Init()
{
lock (this)
{
status_log = "";
}
l_IgnoredAttribs = new System.Collections.ArrayList();
Status = "Initialized";
}
public void Init(string IgnoredAttribs_filename)
{
Init();
System.IO.StreamReader sr = new System.IO.StreamReader(IgnoredAttribs_filename);
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
IgnoredAttribs.Add(line);
}
sr.Close();
}
public void Init(System.Collections.ArrayList IgnoredAttribs_List)
{
Init();
l_IgnoredAttribs = IgnoredAttribs_List;
}
public string Run()
{
CompareXMLs();
if (Status == "Comparisson complete")
{
return Results;
}
else
{
return "ERROR";
}
}
}

This time I just copy-pasted the full class without any further explanation, hopefully it'd help.

P.S.
There might be some error in the cases due to Word's idea of doing things without being asked...

Wednesday, August 11, 2010

SilverBlight

As a first test to the Silverlight I decide to do a simple label + button with a message box popping.
It took a short while to figure that using the “mbox” snippet won’t work (actually because the message box is not in “Forms”). The resulting line of code is:

System.Windows.MessageBox.Show("XXX");

It can be used with caption and buttons parameters, but that’s for a later usage (maybe).
Next step run the project. It appears that the “run-time” environment for debugging is not installed automatically with the SDK, god help M$... Another download, a quick installation and it was over.
Page loads. Page is shown, button is shown, label is gone. Huh???
I repeated the process with different styling, to no result.
Once I added the setting of its content to the “Loaded” of either Layout or the label itself it worked:

private void label1_Loaded(object sender, RoutedEventArgs e)
{

label1.Content = "ZZZ";

}


There was no need to do any styling, just insert the content.
Next, let’s have some fun. Adding a label to the form on the click, assign a function that moves it around on mouse enter.

private void button1_Click(object sender, RoutedEventArgs e)
{

Label x = new Label();

x.Content = "XXXX";

x.FontSize = 30;

x.VerticalAlignment = System.Windows.VerticalAlignment.Top;

x.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;

x.Foreground = new SolidColorBrush(Color.FromArgb(255, 235, 24, 24));

x.Margin = new Thickness(30, 40, 0, 0);

x.MouseEnter += new MouseEventHandler(x_MouseEnter);

LayoutRoot.Children.Add(x);

}


And the events function

void x_MouseEnter(object sender, MouseEventArgs e)
{

if (sender is Label)

{

Label x = ((Label)sender);

double left = x.Margin.Left;

double top = x.Margin.Top;

if (left < 245)

{

left += 10;

} else {

left = 0;

}

top += 10;

x.Margin = new Thickness(left, top, x.Margin.Right, x.Margin.Bottom);

x.Foreground = new SolidColorBrush(Color.FromArgb(255, System.Convert.ToByte(left), 24, 24));

}

}


Nice and easy. Next step put a grid, and fill it with data from an SQL server table.
Hmmmm… That’s not possible directly in Silverlight.
Datagrid component exists in the toolbox. The problem is with the binding. So I used a WCF as data delegate:

private void button1_Click(object sender, RoutedEventArgs e)
{

label1.Content = "Loading...";

svc.GetAccountsCompleted += new
EventHandler(svc_GetAccountsCompleted);

svc.GetAccountsAsync();

}


Note: the call is Asynchronous. There’s no synchronous method for Silverlight WCF.
And the “Completed” method:

void svc_GetAccountsCompleted(object sender, ServiceReference1.GetAccountsCompletedEventArgs e)
{

label1.Content = "Done";

dataGrid1.ItemsSource = e.Result;

}


The WCF (declared on the supporting site) has:

[OperationContract]
public System.Collections.Generic.List GetAccounts()

{

DataClasses1DataContext db = new DataClasses1DataContext();

System.Collections.Generic.List res = new System.Collections.Generic.List();

var tbl = from x in db.TBL_ACCOUNTs where !x.isDeleted select x;

foreach (TBL_ACCOUNT item in tbl.Take(100))

{

res.Add(new AccountClass(item.accountID, item.accountName));

}

return res;

}


public class AccountClass
{

public int AccountID { get; set; }

public string AccountName { get; set; }


public AccountClass(int ID, string Name)
{

AccountID = ID;

AccountName = Name;

}

}


I used LINQ in order to live the DB without junk procedures. For real life DON’T USE IT! It’s by far slower and as might be understood from the code – resource waster.

Last test was multi-threading. Update 3 labels from 3 different threads, non of them is the main thread.
My first version was something like this:

private void button2_Click(object sender, RoutedEventArgs e)
{

System.Threading.Thread th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label1 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label2 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label3 });

}


public void DoThread(object bag)

{

int count = 0;

object[] real_bag = (object[])bag;

Label lbl = (Label)real_bag[0];

while (true)

{

count++;

lbl.Content = count.ToString();

System.Threading.Thread.Sleep(1000);

}

}

It didn’t work (actually I was surprised it compiled…) with a very good reason it’s not thread-safe to manipulate the UI from inside a thread. The problem was that it just terminated the thread without any notice. Thread is alive, poof, no thread exists.

After digging a bit, and adding some beatification the result is:

private static Random r = new Random();
private bool _done = true;


private void button2_Click(object sender, RoutedEventArgs e)
{

if (!_done)

{

_done = true;

}

else

{

StartThreads();

}

}


private void StartThreads()

{

_done = false;

System.Threading.Thread th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label1 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label2 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label3 });

}

public void DoThread(object bag)

{

int count = 0;

object[] real_bag = (object[])bag;

Label lbl = (Label)real_bag[0];

while (!_done)

{

count++;

LayoutRoot.Dispatcher.BeginInvoke(delegate()

{

lbl.Content = count.ToString();

});

System.Threading.Thread.Sleep(r.Next(1000));

}

}


Actually the most important thing in here is the LayoutRoot.Dispatcher.BeginInvoke. That’s the magic command that tells the CLI: use the main thread.

Monday, August 9, 2010

Embedding ASCX web control in a dll

Implementing ascx in a dll is not as simple as it should.
I began by following the steps suggested in this post http://www.timgittos.com/compile-a-user-control-as-a-dll, it partially worked.
I’ve embedded the ascx as resource (build action), deleted the AutoEventWireup and declared all the components. When I tried to access it from the actual website – NOTHING. The component does not exist.
Searching back and forth I’ve found that the namespace is a MUST. If you omit it, it doesn’t show.
The resulting code is something like

namespace MyControls
{

public partial class MyControls : System.Web.UI.UserControl

{

HiddenField hfStatus;

protected void Page_Load(object sender, EventArgs e)

{

String content = String.Empty;

System.IO.Stream stream = System.Reflection.Assembly.
GetExecutingAssembly().GetManifestResourceStream(GetType(), "MyControl.ascx");
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream)
)
{

content = reader.ReadToEnd();

}

Control control = Page.ParseControl(content);

this.Controls.Add(control);

hfStatus = ((HiddenField)control.
FindControl("Status"));
}

}

}


So far so good, but, I had to add a javascript file into the pot.
Plus I wanted this javascript to appear only once on the page, even if the control appears twice or more. In order to achieve this I added a restriction: use of the ID.
But first thing first, meaning, add the Javascript file as embedded resource.
Now move the loading code to OnInit (where it should be placed) instead of Page_Load:
protected override void OnInit(EventArgs e)

{

base.OnInit(e);

String content = String.Empty;

System.IO.Stream stream = System.Reflection.Assembly.
GetExecutingAssembly().GetManifestResourceStream(GetType(), "MyControl.ascx");
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream)
)
{

content = reader.ReadToEnd();

}

Control control = Page.ParseControl(content);

this.Controls.Add(control);

hfStatus = ((HiddenField)control.
FindControl("Status"));
if ((Session["MyControlScript"] == null) || (Session["MyControlScript"].
ToString() == this.ID))
{

Session["MyControlScript"] = this.ID;

ScriptManager.GetCurrent(Page)
.Scripts.Add(new ScriptReference("MyControls.Script.js", " MyControls"));
}

}


And in the AssemblyInfo.cs I added the following line:

[assembly: System.Web.UI.WebResource("MyControls.Script.js", "application/x-javascript")]

And voila! It compiles.

Now all we need is a test page (inside a site)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register TagPrefix="tg" Assembly="MyControls" Namespace="MyControls" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<script type="text/javascript">
function check() {

alert(document.getElementById(
"Cont1_Status").value);
alert(document.getElementById(
"Cont2_Status").value);
}

</script>
<form id="form1" runat="server">
<div>
<input type="button" onclick="check();" value="TEST!!!" />
</div>
<br />
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<tg: MyControl ID="Cont1" runat="server />
<br />
<tg: MyControl ID="Cont2" runat="server />
</div>
</form>
</body>
</html>

note: DON'T USE WORD, it took twice the time just to fix the mess this bloody editor did:(

Sunday, June 13, 2010

Scripting an SQL DB

Lately I've been busy maintaining old systems. The famous .NET 1.1 and alike.
While enjoying a bit of "retro" motion I also saw the giant leaps the new tools did. What impressed me most was the scripting of DB.

Just a few years ago I was in a need for DB structure copy, which would be in the form of a script. The reason for this is simple: automatic creation of the latest DB after the installation without the need for building a new setup script each time an object had changed.
I did the job by building a small utility which ran a "SELECT * FROM sysobjects" with the appropriate flags for tables, keys, procedures and all the other needful stuff.
Lately I needed that ability again, we did some changes to the DB but forgot what they where... So I thought here goes a "deja vue", do a 3 days work again in order to produce a utility like the old one. Luckily for me times changed and so I've found myself ending with a much simpler code:

System.IO.StreamWriter sw = new System.IO.StreamWriter("d:\\diffs.lst");
Microsoft.SqlServer.Management.Smo.Server sServer = new Microsoft.SqlServer.Management.Smo.Server("DBServer1");
Microsoft.SqlServer.Management.Smo.Database sDB = sServer.Databases["DB1"];

Microsoft.SqlServer.Management.Smo.Server dServer = new Microsoft.SqlServer.Management.Smo.Server("DBServer2");

Microsoft.SqlServer.Management.Smo.Database dDB = dServer.Databases["DB2"];

foreach (Microsoft.SqlServer.Management.Smo.Table tbl in sDB.Tables)

{

if (!tbl.IsSystemObject)
{

Console.WriteLine(tbl.Name);
if (dDB.Tables.Contains(tbl.Name))

{

Microsoft.SqlServer.Management.Smo.Table tbl2 = dDB.Tables[tbl.Name];
string scr1 = Decollect(tbl.Script());

string scr2 = Decollect(tbl2.Script());

if (scr1 != scr2)

{

sw.WriteLine(tbl.Name + " - differs");
}

}
else
{

sw.WriteLine(tbl.Name + " - missing");
}

}
else
{

Console.WriteLine("Skipping system table: " + tbl.Name);
}

}
foreach (Microsoft.SqlServer.Management.Smo.StoredProcedure sp in sDB.StoredProcedures)
{

if (!sp.IsSystemObject)
{

Console.WriteLine(sp.Name);
if (dDB.StoredProcedures.Contains(sp.Name))

{

Microsoft.SqlServer.Management.Smo.StoredProcedure sp2 = dDB.StoredProcedures[sp.Name];
string scr1 = Decollect(sp.Script());

string scr2 = Decollect(sp2.Script());

if (scr1 != scr2)

{

sw.WriteLine(sp.Name + " - differs");
}

}
else
{

sw.WriteLine(sp.Name + " - missing");
}

}
else
{

Console.WriteLine("Skipping system proc: " + sp.Name);
}

}

Plus a small helping function to convert the arrays (collections) to strings:

private static string Decollect(System.Collections.Specialized.StringCollection sc)
{

StringBuilder res = new StringBuilder();
foreach (string s in sc)

{

res.Append(s);
}

return res.ToString();
}


Now all I need is something to replace the old backup script:

Option Explicit

Const DB = "DB"
Const User = "sa"

Const Pass = "sa"

Const Server = "(local)"


Call Main()


Sub Main()

If WScript.Arguments.Count=1 Then

Call Restore(WScript.Arguments(0))

Else

Call Backup()

End If

End Sub


Function Restore(BackupFile)

Dim fso

Set fso = CreateObject("scripting.filesystemobject")

If Not fso.FolderExists("D:\backup") Then

fso.CreateFolder("D:\backup")

End If

Dim cmdFile

Set cmdFile = fso.CreateTextFile("c:\backup.sql",True)

cmdFile.WriteLine "DROP DATABASE " & DB
cmdFile.WriteLine "GO"

cmdFile.WriteLine "RESTORE DATABASE " & DB & " FROM DISK = '" & BackupFile & "'"

cmdFile.Close

Set cmdFile = Nothing

Call Exec("osql -U " & User & " -P " & Pass & " -S " & Server & " -i c:\backup.sql -o " & BackupFile & ".log")

Set fso = Nothing

End Function


Function Backup()

Dim BackupFile

BackupFile = "D:\backup\" & DB & DateAsString(Now()) & ".bak"

Dim fso

Set fso = CreateObject("scripting.filesystemobject")

If Not fso.FolderExists("D:\backup") Then

fso.CreateFolder("D:\backup")

End If

Dim cmdFile

Set cmdFile = fso.CreateTextFile("c:\backup.sql",True)

cmdFile.WriteLine "BACKUP DATABASE " & DB & " TO DISK = '" & BackupFile & "'"

cmdFile.Close

Set cmdFile = Nothing

Call Exec("osql -U " & User & " -P " & Pass & " -S " & Server & "-i c:\backup.sql -o " & BackupFile & ".log")

Set fso = Nothing

End function


Function Exec(pCommandLine)

On Error Resume Next

Dim WshShell : Set WshShell = WScript.CreateObject("WScript.Shell")

Exec = WshShell.Run(pCommandLine,,True)

If Err.number <> 0 Then

Exec = 255

Err.clear

End If

End Function


function DateAsString(ReqDate)

dim res

res = ""

res = res & year(ReqDate)

res = res & month(ReqDate)

res = res & day(ReqDate)

res = res & hour(ReqDate)

res = res & minute(ReqDate)

res = res & second(ReqDate)

DateAsString = res

end function

Monday, March 15, 2010

Javascript bug

I happened to pass across a tiny non-significant bug in Javascript just the other day.
Well, non-significant is in the eye of the beholder... The problem was simple, the user puts an amount to charge using a credit card, verifies the deal is OK from the credit company and does the deal. Nothing special about the process, trust me I've done a few programs which do it. The difference in this case was the interface to the credit company's server, they didn't want a float so we had to send the amount as cents (or agorot in Israel), simple isn't it? Take the amount, multiply by 100, and do whatever.
Simple it isn't. Try to put this line in an HTML script:

alert(parseInt(parseFloat('70.24')*100))

or if you're too lazy just use the following HTML code:

<html>
<head></head>
<body onload="alert(parseInt(parseFloat('70.24')*100))">
</body>


As far as I know 70.24*100 = 7024:) We've found a second problem with 277.9, which has exactly the same bug.

As usual for matters of this kind the solution is to use a "different" approach:

var dol = parseInt(amount.split('.')[0])*100;
var cen = 0;
if (amount.split('.')[1] != null)
{
cen = parseInt(amount.split('.')[1]);
}
if (dol < 0)
{
return dol - cen;
} else {
return + cen;
}


It might not be the best approach, but it certainly does the job. Unless someone fixes the Javascript's variables' accuracy, I'll stick to this solution for this problem.