All blog posts of month 8 of the year 2010


NAppUpdate - Application auto-update framework for .NET

.NET, English posts, NAppUpdate, WinForms, WPF Comments (11)

Any desktop applications programmer finds himself every now and then looking for a library providing an application auto-update functionality, that is flexible and easy-to-integrate. I had the intention of working on such a library for some time now, so it will do things the way I want it to. Last week I got to actually doing it, and I'm quite satisfied with the results.

NAppUpdate can be integrated easily with any .NET application - WinForms or WPF, and it offers great flexibility through interfaces, for feed readers, file sources, and update tasks. It also supports conditional updates, the implementation of which offers unlimited extendibility.

I based my work on Lee Treveil's .NET-Auto-Update, and although I practically rewrote almost everything, he certainly deserves this credit.

The library, released under the Apache 2.0 software license, can be downloaded / cloned from http://github.com/synhershko/NAppUpdate. Please share your thoughts, or any extension you make to it.

Update 30/03/2011: A user/developer discussion group is now available at: http://groups.google.com/group/nappupdate

Update 30/09/2010: although most of the post below is still true, it is already (!) a bit outdated. Please check the NAppUpdate tag in this blog for the latest information on the library.

At the time of this writing, here is what NAppUpdate offers:

  1. Seamless integration - one DLL to reference, a few lines of code in the right places and you are all set.
  2. Requires .NET 2.0 and up. No other silly requirements.
  3. Cold updates supported. The updater executable used for replacing files that cannot be hot-swapped is packaged within the library's DLL, and is self-deleting, once done updating.
  4. Shoot and forget, just tell NAppUpdate to prepare and apply your updates once you have notified of their existence, and it will do everything for you, including restarting your application after a cold update.
  5. State-based update process (Did / didn't check for updates; prepared updates; applied updates successfully; rollback required). You can quit any time you wish.
  6. Updates are tasks, so practically every type of update is supported. Right now the only implemented task is FileUpdateTask, used for simple file download and replacement. Implementing more tasks is very easy, and can be done right from your application simply by implementing an interface, without any extra work.
  7. Potentially supported tasks include registry updates, database updates (schema and/or data), Lucene index changes, diff patches, and virtually anything you can imagine.
  8. Backup and rollback are supported in the task level, giving you full protection against failures.
  9. Conditional updates - every task can have unlimited number of conditions attached to it. Conditions are evaluated in a boolean manner, and can be grouped together with AND, OR'd, or being defined as NOT. If the conditions aren't met, the task is being ignored.
  10. Just like with tasks, custom conditions can be created very easily by implementing an interface with your own logic, within your very own application.
  11. Condition types supported at the time of this writing include file properties checks (exists, assembly version, size, date, SHA256 checksum) and OS bitness condition (32/64 bit). More planned conditions include registry checks, more OS checks, prerequisites checks (.NET version and other common applications), etc.
  12. Multiple update feed formats, including Adobe Appcast and our very own NAppUpdate XML (NauXml). You can create your own by simply implementing IUpdateFeedReader.
  13. Abstract download sources allow you to create your own implementation for downloading feeds and update files / packages. Currently we have SimpleWebSource and MemorySource implemented. Your own implementations can include whatever other source you want, for example transfer files over BitTorrent or other P2P protocol.

Here are some code snippets, taken from the WinForms sample application. First is the method checking for updates (UpdateManager.Instance was loaded with some configurations in the Form constructor):

// Get a local pointer to the UpdateManager instance
UpdateManager updManager = UpdateManager.Instance;

// Only check for updates if we haven't done so already
if (updManager.State != UpdateManager.UpdateProcessState.NotChecked)
{
    MessageBox.Show("Update process has already initialized; current state: " + updManager.State.ToString());
    return;
}

// Check for updates - returns true if relevant updates are found (after processing all the tasks and
// conditions)
if (updManager.CheckForUpdates(source))
{
    DialogResult dr = MessageBox.Show(
        string.Format("Updates are available to your software ({0} total). Do you want to download and prepare them now? You can always do this at a later time.",
        updManager.UpdatesAvailable),
        "Software updates available",
         MessageBoxButtons.YesNo);

    if (dr == DialogResult.Yes)
    {
        updManager.PrepareUpdatesAsync(OnPrepareUpdatesCompleted);
    }
}
else
{
    MessageBox.Show("Your software is up to date");
}

... and after a notification on the updates has been sent, and the updates were prepared for installation, here is how this is done:

UpdateManager updManager = UpdateManager.Instance;

DialogResult dr = MessageBox.Show(
        "Updates are ready to install. Do you wish to install them now?",
        "Software updates ready",
         MessageBoxButtons.YesNo);

if (dr == DialogResult.Yes)
{
    // This is a synchronous method by design, make sure to save all user work before calling
    // it as it might restart your application
    updManager.ApplyUpdates();
}

More to come soon...


Better splitter behavior for HTMLayout

.NET, English posts, HTMLayout Comments (0)

The implementation of the splitter behavior which comes with HTMLayout, and its .NET wrapper in Nabu, suffers from several issues: it doesn't take into account RTL layouts, and the actual resizing is done while dragging the splitter itself - which is quite costly. As a result of the latter, 2 on_size notifications were pushed for every sizing operation instead of one.

I perfected it so it uses a "ghost" element to mark the splitter's position while dragging, and the actual layout changes occur only once its released from capture. Also, it now works correctly in RTL layouts, and many graphical glitches are prevented.

The code below is meant to be used with the .NET Nabu wrapper; porting it to be native HTMLayout C++ behavior is quite easy. Some more generalization work may be required before you can implement it in your solution.

using System;
using System.Collections.Generic;
using System.Text;
using Nabu.Forms.Html;

namespace MyApp.UI
{
    [HtmlBehaviorName("splitter")]
    public sealed class HtmlSplitterBehavior : HtmlBehaviorBase
    {
        #region HtmlSplitterBehavior

        #region Fields

        private bool _isCaptured;
        private bool _isHorizontal;
        private bool _isRTL;
        private HtmlTag _parent;
        private HtmlTag _prevSibling;
        private HtmlTag _nextSibling;
        private string _className;
        private int _startPrevSize;
        private int _startPosition;

        #endregion

        #region Methods

        public HtmlSplitterBehavior()
            : base(HtmlEventGroup.InitializationAndMouse)
        {
        }

        #endregion

        #endregion

        #region HtmlBehaviorBase

        protected override void OnMouse(HtmlTag sender, HtmlMouseEventArgs e)
        {
            if ((e.MouseState & HtmlEventMouseState.Left) != 0)
            {
                switch (e.Command)
                {
                    case HtmlMouseEventCommand.Down:
                        {
                            using (HtmlTag splitter = e.Target)
                            {
                                HtmlTag parent = splitter.Parent;
                                HtmlTag leftSibling = splitter.PrevSibling;
                                HtmlTag rightSibling = splitter.NextSibling;

                                if ((parent != null) && (leftSibling != null) && (rightSibling != null))
                                {
                                    this._parent = parent;
                                    _isRTL = splitter.GetState(HtmlTagState.IsRTL);

                                    this._prevSibling = leftSibling;
                                    this._nextSibling = rightSibling;
                                    this._isHorizontal = this._parent.GetStyle("flow") == "horizontal";

                                    if (this._isHorizontal)
                                    {
                                        splitter.SetStyle(_isRTL ? "left" : "right",
                                                                    e.PositionInDocument.X.ToString() + "px");
                                        this._startPosition = e.PositionInDocument.X;
                                        this._startPrevSize = this._prevSibling.GetLocation(HtmlTagArea.RootRelative).Width;
                                    }
                                    else
                                    {
                                        splitter.SetStyle("top", e.PositionInDocument.Y.ToString() + "px");
                                        this._startPosition = e.PositionInDocument.Y;
                                        this._startPrevSize = this._prevSibling.GetLocation(HtmlTagArea.RootRelative).Height;
                                    }

                                    splitter.SetStyle("height", this._parent.GetLocation(HtmlTagArea.BorderBox).Height + "px");

                                    this._className = splitter.Attributes["class"];
                                    splitter.Attributes["class"] = "splitter_ghost";

                                    splitter.SetCapture();
                                    this._isCaptured = true;
                                    e.IsHandled = true;
                                }
                            }
                        }
                        break;
                    case HtmlMouseEventCommand.Up:
                        if (this._isCaptured)
                        {
                            using (HtmlTag splitter = e.Target)
                            {
                                splitter.Attributes["class"] = _className;

                                if (this._isHorizontal)
                                {
                                    splitter.SetStyle("height", null);

                                    int prevSize = this._startPrevSize;
                                    if (_isRTL)
                                        prevSize += (e.PositionInDocument.X - this._startPosition) * -1;
                                    else
                                        prevSize += (e.PositionInDocument.X - this._startPosition);

                                    this._prevSibling.SetStyle(
                                        "width",
                                        prevSize + "px");
                                    this._nextSibling.SetStyle(
                                        "width",
                                        "100%%");
                                }
                                else
                                {
                                    int prevSize = this._startPrevSize + (e.PositionInDocument.Y - this._startPosition);

                                    this._prevSibling.SetStyle(
                                        "height",
                                        prevSize + "px");
                                    this._nextSibling.SetStyle(
                                        "height",
                                        "100%%");
                                }
                            }

                            this._parent = null;
                            this._prevSibling = null;
                            this._nextSibling = null;

                            HtmlTag.ReleaseCapture();
                            this._isCaptured = false;
                            e.IsHandled = true;
                        }
                        break;
                    case HtmlMouseEventCommand.Move:
                        if (this._isCaptured)
                        {
                            using (HtmlTag splitter = e.Target)
                            {
                                splitter.SetStyle(_isRTL ? "left" : "right", e.PositionInDocument.X.ToString() + "px");
                            }

                            e.IsHandled = true;
                        }
                        break;
                }
            }

            base.OnMouse(sender, e);
        }

        #endregion
    }
}

The splitter element is defined in my CSS as follows:

#mainsection > hr {padding:0;border:none;margin:0;width:0; height:*;hit-margin:3px; behavior:splitter;cursor:w-resize;}
#mainsection > hr.splitter_ghost { border: 1px solid #999; position:absolute;}

Showing 3 posts out of 3 total, page 1