The Aw Bus Remote Client

September 11, 2009

All of the Wcf contracts for Aw Bus are in an assembly by themselves. That assembly will be distributable. The admin program uses the service contract types directly rather than creating a proxy.

To use the channel factory, you need to specify the binding, address, contracts, etc. There’s a lot of setup there.

I usually wrap this up into an object than does the work for me. The constructor creates the service; the dispose destroys it.

This snippet is used to retrieve a list of all of the services hosted on a particular node.

NOTE: I don’t know why the code samples aren’t rendering correctly.

 BusServiceDescription[] descriptions; using (RemoteClient<INodeService> client1 = RemoteClient<INodeService>.GetClient("NodeAdmin")) { 	descriptions = client1.Proxy.GetServiceDescriptions(""); } tvOverview.Invoke(new MethodInvoker(delegate 

“NodeAdmin” is the name of the service. I append that to the base uri. Later, it will be used to determine all of the things it needs to properly connect to the service.

It handles all of the binding information, etc. Currently, all of the necessary goodies are hard coded. This will seriously evolve as new types of endpoints are exposed. There will be an endpoint provider on the back end.

I’ve used this wrapper approach before. I thought there was a problem with the dispose, though. We saw that if an exception occurs within the using clause, then another exception occurs within the dispose, the first exception is lost and the second bubbles up. We hacked a solution to that i’m not proud of, but it works.

When I built this new wrapper for Aw Bus, I set out to conquer that problem elegantly. And I did so, which I’ll get to. I started to prepare some code samples to demonstrate the problem and the solution; and wouldn’t you know it… I couldn’t reproduce it. I think I understand why, but what I don’t yet understand is why I didn’t figure it out before. I have to look at the other wrapper and see if I’m remembering it correctly. Simply putting a catch-all in the dispose allows the original exception to bubble up.

Anyway. Prior to realizing I didn’t need a solution, I built the solution. The solution no longer appears to be useful for that problem, but will be helpful for other purposes. Basically, I use reflection to inspect the service contract, then generate my own implementation of that interface which will call the service. Its an on-the-fly proxy generator. I control the code that gets generated, so I can put in any injection points or tracking that I’d like.

The generated type is stored in a cache, so it only gets generated once as long as each call uses the same cache. That will all be hidden form the user anyway; RemoteClient will take care of it.

Example:

INodeService is the service contract.

I generate INodeServiceProtectedProxy, which implements the interface. (I will drop the leading I.) The constructor creates the channel factory and the binding. The methods call the method on the channel. The dispose closes the channel, etc.

Here’s an example of a generated proxy. This is pretty minimal. But, as I said, its all template based. So, I can just slap around the template and have it do anything I want to. (I haven’t implemented the dispose pattern yet.

using System; using System.Collections.Generic; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels;  namespace AllardWorks.AwBus.RemoteClient.ProtectedProxy {     public class INodeServiceProtectiveProxy : AllardWorks.AwBus.Contracts.Services.INodeService, IDisposable     {         private readonly ChannelFactory<AllardWorks.AwBus.Contracts.Services.INodeService> _proxy;         private readonly AllardWorks.AwBus.Contracts.Services.INodeService _instance; 		 		// temporary. remove soon 		public INodeServiceProtectiveProxy() 		{ 		} 		         public INodeServiceProtectiveProxy(Binding binding, string url)         {             _proxy = new ChannelFactory<AllardWorks.AwBus.Contracts.Services.INodeService>(binding, new EndpointAddress(url));             _instance = _proxy.CreateChannel();         }  		             [OperationContract]             public AllardWorks.AwBus.Contracts.BusServiceDescription[] GetServiceDescriptions (System.String token)             {                 try                 {                     return  _instance.GetServiceDescriptions(token);                 }                 catch                 {                     Close();                     throw;                 }             }                                  [OperationContract]             public AllardWorks.AwBus.Contracts.RemoteWcfEndpointDescription[] GetAdminServiceEndpoints (System.String token, System.String serviceName)             {                 try                 {                     return  _instance.GetAdminServiceEndpoints(token, serviceName);                 }                 catch                 {                     Close();                     throw;                 }             }                                  [OperationContract]             public AllardWorks.AwBus.Contracts.RemoteNodeDescription GetNodeDescription (System.String token)             {                 try                 {                     return  _instance.GetNodeDescription(token);                 }                 catch                 {                     Close();                     throw;                 }             }                      		         private bool _closed;         private void Close()         {             if (_closed)             {                 return;             }             try             {                 _proxy.Close();             } 			catch 			{ 			}         }          private bool _disposed;         public void Dispose()         {             Close();         }     } } 

Aw Bus Update

September 11, 2009

image

Progress continues

- finished the RemoteClient so that it is sufficient for current needs. Its going to continuously evolved.

- created an MDI form – no big deal there. IT comes out of the box as shown; I just added the base url text box. I created an ISessionContext interface that wraps up all of the things from the parent form that the other forms may need. That will be passed to all new child forms.

- Changed all communication to Wcf. The form was using a local server object to start with.

- Separated the server from the form. Now that its all Wcf, the form app is strictly a client

- Added a GetNodeInfo() method to the NodeAdmin service. Its results are shown in the lower right corner.

You’ll see that the container service is currently stopped. That’s because I deleted one file too many during a cleanup. I lost the ContainerStore.xml, which is the default container store. I haven’t retrieved it back from Svn yet. Whoops.

What’s next?

Currently, the server is hard coded to start as Node 1. I have to break that up so that I can start multiple nodes. The console host will allow you to pick which one you want to start, etc.

I have a couple things I want to do after that, but I’m pretty sure the one I’ll actually focus on will be the drag-and-drop of packages from one node to another.

Something I should do before that (but won’t because it won’t be as much fun) is expose the logging. I had been planning to do that via the pub-sub, but since that’s not progressing right now, I may look at other possibilities. Right now, in the screenshot, it would obviously be good to know why the container service didn’t start.


AwBus – First Screenshot

September 7, 2009

image

I’ve been spending a lot of time working on a service bus. I have a lot of goals for it; they’re all based on it being extremely simple. When you unzip it, you can launch the executable and be up and running without any additional configuration. There will be a lot you can do and should do, but nothing will be required to get started.

The bus contains one or more nodes which exists in one or more processes on one or more machines. You can drag and drop containers and individual packages from one node to another; it will continue to exist in the old node until all pending requests are complete. In the meantime, the broker will switch to the new one for new requests.

Each node contains services that the node needs. 4 of them are shown above;

  1. Wcf Host Service – manages all of the Wcf endpoints.
  2. App Domain Host Services – every package runs in its own app domain. Additionally, temporary app domains are used to explore all of the assemblies in a particular folder to determine what things should be auto-started. (Currently, self starts include Subscribers. Wcf services are next)
  3. The container service manages all of the containers in the node. A container is a home for a package. A package can contain one or more pieces of functionality; publisher, subscriber, Wcf service, etc
  4. Folder service – When a package is deployed, it is deployed to a folder. The folder service keeps track of what folders have been created and why

Each bus service can be decorated with an attribute that identifies a Wcf admin service. The screenshot shows that the Wcf Host Service and Container Service each have an admin service. Node service is also an admin service, but its admin for the node itself, not a particular service.

Currently, the containers node is empty. I’m converting that to retrieve the container info remotely.

The very early stages of the pub-sub are in there, but it only works on the local node.

I’ve been focusing on the client for the last couple days. Some of the things in the screen shot are built from references to the server object. Others are retrieved via Wcf. It should be via Wcf, so I’m moving them over. To facilitate that, I had to start building the client. (You can create any old Wcf client for the service, but I wrapped it up with some bus specific functionality.)

I’m pretty excited about this project. Let’s see how it goes. So far, I’m very pleased with its progress.

More to come.


Code Test

September 6, 2009

 

I’m switching to WordPress.

Trying a code sample….

namespace testing
{
    public class Something\
    {
        public void Whatever()
        {
        }
    }
}

So what’s the deal with the thread blocking in vs2010/.net4?

September 3, 2009

This was weird. I didn’t say anything in last night’s post because I wanted to make sure I wasn’t insane. I’m pretty sure I’m not.

Anyhoo, I created a stupid little windows form application to host my Wcf Service and to also to create a client.

The PAGE_LOAD calls a StartServer() method, which creates the service host.

The form has a TEST button that creates a WCF client to the same service.

That’s all pretty common stuff; start a server, create a client. But, the client kept reporting a Wcf timeout after 1 minute. I was able to connect to the service from another process, but not from within the windows form. It seemed like maybe a threading issue.

So, I changed the client to run in another thread, and it worked. Why’s that?


Injecting into a Wcf Service

September 2, 2009

 

Update 9/2/2009: I forgot to include the service contract. Added it.

Overview

When you setup a Service Host, WCF manages the creation of your services. It requires there to be a default constructor so that it can just instantiate the thing and be done with it.

I often find myself wishing to be able to pass something into the object when its created. For example: a unity container. I’d like to inject the unity container.

Lacking that ability, we end up with a static container somewhere that the service calls.

public class TestService : ITestService
{
    public void DoSomething() 
    {
        IWhatever whatever = StaticHelper.Container.Resolve<IWhatever>();
    }
}


What’s wrong with that? Statics are evil. Whenever you use a static, there should be a big red flag. Sure, its convenient, but its not very testable or flexible.

Sometime we can somewhat counter the testability limitation as follows

public class TestService : ITestService
{
    private readonly IUnityContainer _unityContainer;

    public TestService() 
    {
        // wcf will use this constructor
        _unityContainer = StaticHelper.Container;
    }

    // tests use this constructor
    public TestService(IUnityContainer container) 
    {
        _unityContainer = container;
    }  

    public void DoSomething() 
    {
        IWhatever whatever = _unityContainer.Resolve<IWhatever>();
    }
}

That works, but still smells kind of funny.

Tonight’s adventure was to figure out how to pass a unity container to the test service when instantiated. I didn’t really know where to start on this, so started at the ServiceHost, and googled the night away.

There are a bunch of examples out there to use unity to determine the implementation type of the service. Two examples that I looked at rely on a static IOC container in order to determine the implementation type. In those cases, the statics can be easily eliminated just by passing the container through the layers as I do here. (Their goal’s were different. My goal is to get rid of the statics.)

UPDATE: Now that I’ve gotten it all to work, and I’ve spent some time thinking about the conclusion, the fore mentioned approach is the best way. Rather than pulling objects from unity and assign it to properties, just define the service itself in the container. Then unity can handle all of the injection. I had to write this blog to come to that conclusion, so I’m not going to throw it all away.

Basic Steps

  1. Create a service host

  2. Create a behavior

  3. Create an instance provider – this is what we’re really interested in. This creates the service object. We need to get the IUnityContainer to this guy.

  4. Connect the host to the behavior

  5. Connect the behavior to the instance provider

I created a subclass of ServiceHost and added an IUnityContainer parameter to both of the constructors. The parameter value is saved to a local variable.

The service behavior has no knowledge of the IUnityContainer. It just gives us the bridge between the instance provider.

The instance provider has access to the service host. As you may recall, the service host has the IUnityContainer property. If the service object (implementation) has a IUnityContainer property, then we assign it.

The Code

DISCLAIMER: This code is just enough to get it to work. Its not tied up nice and neat.

An Interface for Services that require an IUnityContainer

The instance provider, way down at the bottom, will see if the service implements this interface. If so, it assigns the property.

namespace AllardWorks.AwBus.Contracts
{
    public interface IUnityWcfService
    {
        IUnityContainer UnityContainer { get; set; }
    }
}

The Service Host

    public class AwBusServiceHost : ServiceHost
    {
        public AwBusServiceHost(IUnityContainer container)
            : base()
        {
            _container = container;
        }

        public AwBusServiceHost(IUnityContainer container, Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
            _container = container;
        }

        protected override void OnOpening()
        {
            Description.Behaviors.Add(new AwBusServiceBehavior());
            base.OnOpening();
        }

        public readonly IUnityContainer _container;
        public IUnityContainer UnityContainer
        {
            get { return _container; }
        }
    }


The Behavior

We’re only interested in the ApplyDispatchBehavior method.

This loops through all of the endpoint dispatchers and sets the instance provider to one of our own design. We pass it the type that is to be instantiated.

    public class AwBusServiceBehavior : IServiceBehavior
    {
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher cd = cdb as ChannelDispatcher;
                if (cd == null)
                {
                    continue;
                }

                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.InstanceProvider = new AwBusInstanceProvider(serviceDescription.ServiceType);
                }
            }
        }

        #region Not Used
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }
        #endregion
    }

The Instance Provider

At last, we get to create an object. Yay.

The interesting part here is the IUnityWcfService. If the new object implements that interface, then we set the UnityContainer property that the interface provides.

    public class AwBusInstanceProvider : IInstanceProvider
    {
        private readonly Type _type;

        public AwBusInstanceProvider(Type type)
        {
            _type = type;
        }

        public object GetInstance(InstanceContext instanceContext)
        {
            return GetInstance(instanceContext, null);
        }

        public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
        {
            object service = Activator.CreateInstance(_type);
            IUnityWcfService unityService = service as IUnityWcfService;
            if (unityService != null)
            {
                unityService.UnityContainer = ((AwBusServiceHost)instanceContext.Host).UnityContainer;
            }
            return service;
        }

        public void ReleaseInstance(InstanceContext instanceContext, object instance)
        {
        }
    }

Take it for a Test Drive

A Test Service. (Hand coded on the spot. Should be mostly correct though)

[ServiceContract]

public interface IDoSomething

{

[OperationContract]

bool HasContainer();

}

 

public class DoSomething : IDoSomething, IUnityWcfService{

public IUnityContainer UnityContainer { get; set; }

public bool HasContainer()

{

return UnityContainer != null;

}

}

The Server

        private IUnityContainer _unity;
        private ServiceHost _host;
        private void StartServer()
        {
            _unity = new UnityContainer();
            _host = new AwBusServiceHost(_unity, typeof(TestService), new Uri[] { });

            #region Mex
            BindingElement mexBindingElement = new TcpTransportBindingElement();
            CustomBinding mexBinding = new CustomBinding(mexBindingElement);
            ServiceMetadataBehavior metadataBehavior = _host.Description.Behaviors.Find<ServiceMetadataBehavior>();
            if (metadataBehavior == null)
            {
                metadataBehavior = new ServiceMetadataBehavior();
                _host.Description.Behaviors.Add(metadataBehavior);
            }
            _host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, ServiceUri + "/MEX");
            #endregion

 

            #region NetTcp Endpoint
            c.Binding binding = new NetTcpBinding(SecurityMode.None);
            _host.AddServiceEndpoint(typeof(ITestService), binding, new Uri(ServiceUri));
            #endregion

            _host.Open();
        }

The Client

                c.Binding binding = new NetTcpBinding(SecurityMode.None);
                EndpointAddress address = new EndpointAddress(ServiceUri);
                using (ChannelFactory<ITestService> client = new ChannelFactory<ITestService>(binding, address))
                {
                    ITestService proxy = client.CreateChannel();
                    MessageBox.Show(proxy.HasContainer().ToString());
                }

Conclusion

This is pretty neat. This solves an inconvenience we’ve had at work, though at work we need a different object. We have this thing we call “the factory”, which is our own IOC container. We wrote it before unity was released and before we realized that it already a name (IOC).

Once you understand that you can pass anything you want to the service host, and then eventually pass it down to the service object, there are a lot of other injection possibilities. Maybe rather than pass the entire container to the service, you just pull out the objects the service needs an inject them. There are a few ways to do that, but after a few seconds of thought, I think that using unity to construct the service object itself is the best idea. (I found a few examples of this, so its an established approach.)


Something more secure than Notepad

June 2, 2009

For years, I’ve stored all of my passwords in a TXT file. TXT files are inherently secure because you have to double click it in order to view it. Most people don’t know that (until now). To further complicate things, I have obfuscated the file name by calling it “passwords.txt”. Its a boring name that will not pique interest. As a computer professional, I’m sure you’ll agree that I have taken all reasonable steps towards securing my personal information.

But, the notepad file is inconvenient. I keep it on an external drive that’s plugged into my home computer. I hardly use my home computer. So, when I need to lookup a password, its a pain. (I have most of them memorized, but Bank of America, for example, is just a bunch of numbers.)

Last night, I started looking for an online notepad service where I can type in all of my information and save it on someone else’s server preferably using the honor system. Surprisingly, such sites are not abundant, but I stumbled across the concept of “online password storage”. There are several sites out there, obviously managed by paranoid maniacs, that think simply storing passwords in clear text is not the way to go. So, they do things like “encrypt” and require you to “login”. Its pretty bizarre. These guys are taking security way too seriously. Honestly, what is someone really going to do if they get a list of all of my credit card numbers and pins and social security number?

I looked at a few options, but ended up trying http://passpack.com. You can store 100 passwords for free. Each password record gives you plenty of fields, including a free form note field, to put in anything you need. This is good for the banks since I usually track more info than just the site’s id and password. (Ie: cc number, expiration date, ccv, etc.). It gives you quick links to copy information from the record into the clipboard.

You login to the site using your Id and password. But, before you can get to your needlessly encrypted highly personal data, you have to type in your UNPACK password. It seems that they’re using this to hash the data. If you lose the UNPACK password, you’re done. They can’t provide you a new once (hence my speculation that it must be hashed.)

The site is clean and makes good use of ajax. Pretty much every time you click a link, though, you get a fancy shmancy progress bar. I don’t like that. The next page shouldn’t take so long to load that it needs a progress bar.

So far, I really like it. I have entered 4 passwords, which means I have less than 100 free passwords remaining because I already used four of them as I already stated at the beginning of this major run-on sentence. I’m not going to go nuts and figure out, exactly, how many more that leave me, but I have at least 20 more to go of the 100, which is enough for me.

It offers many advanced features, such as sharing and messaging, but I haven’t played with those. I’m really only interested in using it for personal uses.

Check it out: http://passpack.com


Serious memory leak with TRUE in .Net

May 30, 2009

Greetings

At work, we’ve been running PSR tests against our primary application. Last night, the application (which runs as a windows service), was on an obvious spiral into the drain of despair. The memory was steadily increasing. When it reached 1.5 gigs, the application stopped responding altogether.

That’s not good.

We were going to just not say anything and hope for the best, but the more level headed of us thought that maybe we should take a look. I was completely against it and wanted to sacrifice a goat instead, but I was overruled. (I seem to get overruled every time I want to sacrifice animals.)

I started by reviewing the code. I found some connection objects that weren’t being properly cleaned up. I ordered 50 verbal lashes for the offending perps, but moved on. It wasn’t significant enough to be the memory leak, but did sprout an emotional leak in the bowels of my soul. (Please, just use the using clause. That’s all I ask.)

But I digress. Today, we ran ANTS against it to see where all that memory was all going. It didn’t help, which is unusual.

So, we did it the old fashion way. We started commenting things out of the main method to find which one was the problem. The object does:

  1. Receives an xml message
  2. Removes duplicate nodes
  3. Calls a stored procedure to get more information for each of the nodes
  4. Does an Xslt Transform
  5. Publishes the message via WCF

Of all the things there, I was most suspicious of the WCF client and least suspicious of XSLT. Imagine my chagrin when it turned out to be the XSLT method.

// XSLT

XslCompiledTransform transform = new XslCompiledTransform(true);

using (XmlReader xmlReader = new XmlTextReader(xsltFile))

{

transform.Load(xmlReader);

}

 

The problem is the TRUE parameter when the object is instantiated. If you remove it, or change it to false, then everything is stable.

This is a surprisingly large bug. How can something as fundamental as the boolean value TRUE take down an application like that? Shouldn’t someone have tested that the booleans work properly before shipping? The very foundations of computing are based on boolean values; bits are either on or off… yes or no… 1 or 0. This isn’t rocket science! How can Microsoft ship a product that doesn’t fully support the word true?

I haven’t been this bothered about the .Net framework since I learned that the number 0 is also broken. Every time I try to divide any number by it, it breaks with some illogical math error (can’t divide by zero?). I can’t say that I’ve tried to divide every number, but  I did get the vast majority. .. let’s just say that I’ve tested the theory enough times to be convinced that it is a problem.

I really love .NET and will continue to use it without reservation. But, the tough lesson is that you can’t take it for granted, especially if your logic is based on evaluations of some sort. Now that we’ve identified its limitations, we can use it more effectively.

If the problem was elsewhere… let’s say, if the problem were in the XslCompiledTransform object rather than the word true, that would be more understandable. Then we might be able to conclude that “XslCompiledTransform in debug mode leaks more than the Bush administration”. That’s something we could get our head around and come to terms with… But a broken true!? Dissapointing.


SpamArrest – You Got Me

May 25, 2009

I’ve been using SpamArrest.com for years now. I’ve been a big fan of the service, despite its flaws. But, as the years wane on and the flaws continue to persist uncontested, I have become less of a fan. In fact, I officially declare myself no longer a fan.

Here is a big related post from April 2008: http://hamletcode.blogspot.com/2008/04/email-decision.html

I was experiencing email turmoil at the time, and settled on SpamArrest. I would’ve liked to use Gmail, but could not (at least not the way intended).

Since then, 2 significant things have happened with spam arrest:

1 – I bought the lifetime subscription. At the time, I was still a fan. I’ve been using it for years and planned to use it for years more.

2 – They introduced a javascript bug. I reported the bug to them on October 27th 2008 after waiting serveral weeks to see if they would correct it themselves.

My Email to them

Greetings

This has been happening for quite a while now, but I waited incase it was going to be fixed, but it hasn’t.

There’s a bug on the login screen. It attempts to attach an event to the REMEMBER ME checkbox. But, if you’re already logged in (because it remembered you), then the control doesn’t exist, so it can’t attach the event. This results in an error dialog:

A runtime error has occurred.
Do you wish to debug?
Line 1726
Error: ‘addEventListener’ is null or not an object.
Its not a big deal, but after a few weeks its getting a little annoying. (I’m a developer, so I can’t disable the alerts.)

Jay

Rather than trying the steps listed, they instead suggested that my browser must be “acting up”, and suggested that I install firefox or chrome. That’s great… thanks. Great advice. I responded pointing out exactly where the javascript was failing and why, and showed that if fails in firefox 3 too. I listed 3 bulleted steps to reproduce. They responded saying “thank you, we’ll forward that to our development team”.

That was nearly 7 months ago. They still haven’t fixed it. Its going to fail in every browser because it’s just bad logic. When you’re already logged in, there isn’t a “REMEMBER ME” checkbox. The javascript is looking for it anyway, then fails when it can’t find it.

Another of my favorite problems is REPLY ALL. If you click REPLY ALL, and forget to remove your own name from the address list, you get an onslaught of spam messages. Once you delete the messages, you don’t get any more, so its not like it auto-approved them. Its just that you get a whole bunch that you have to cleanup before you’re back to normal.

In my previous blog post, I mentioned the searches I conducted in their help system to find simple things like IMAP and DISK QUOTA. To find DISK QUOTA information, you have to search for “SPACE”. To find IMAP information, you have to read an article entitled “WHAT IS A POP SERVER?” Well, I already know what a pop server is. If I’m looking for IMAP server information, why would I click that?

I strongly regret the life time subscription. I asked for a refund on it, and they responded saying no, but then asked me what the problems were. I didn’t answer. How many times do I need to tell them what the problems are? At this point, I continue to use SPAM ARREST only out of laziness, despite the life time subscription. Maybe I can sell it on EBAY or something.

Now, the webmail client has a REFER FRIEND tab on which they ask me to refer business to them for savings. I already have a strongly regretted life time subscription… how does refering a friend to spam arrest help me? Furthermore, even if I was still a fan, then that tab would be useless anyway. I’ve been referring people to SpamArrest for years, not because I want some type of credit, but because it’s a good service. Only a few of those referrals have actually started using it, but that wasn’t due to a lack of effort on my part. (Most people find GMAIL junk filters to be sufficient.)

My official stance has changed. I will no longer recommend Spam Arrest. Infact, I will actively express dissapointment. Software is supposed to evolve; their web client continues to be stagnant (and broken) where it counts. The pages still say COPYRIGHT 2006!!! Has anyone looked at a calendar recently?

Its ok to have flaws. Its not ok to never fix them. I’m dissapointed in them for not evolving, and I’m really annoyed that I fell for the “lifetime subscription” scam.


Chewing

May 11, 2009

In order to chew, you need teeth.

Jack now has one that finally broke the gum line. This means that he can chew very small, very localized foods!