Author Archives: bjornkuiper

OpenMSX and RetroPie

This post outlines how I got OpenMSX running on RetroPie, basically it is a collection of links and tips and tricks that i gathered from other sites to make it work.

Quick overview:
– OpenMSX 0.14.0
– RetroPie 4.3
– Raspberry Pi 3 Model B

I bought my Raspberry Pi 3 Model B Essentials kit (element14.com) from a Dutch online-store (1).

Step 1: Assemble Raspberry Pi
Follow the instructions provided with your purchase and assemble your Raspberry Pi

PRO-TIP: You might need or want to re-partition your SD card if it has been used before. Under Windows you can use “Disk Management” to do this.

Step 2: Install RetroPie on the SD card for your Raspberry Pi
For this I followed the “First Installation” instructions on the RetroPie homepage (2). So I downloaded the latest RetroPie image for the Raspberry Pi 3 (download | mirror) and because I’m using Windows I used Win32DiskImager to write the image to the SD card. Make sure your SD card is set to “read/write” and not just “read-only”. The configuration of the controllers is not that important as OpenMSX uses their own settings. Just make sure you set “A” to match with “A” on your keyboard, etc. so you can easily navigate the EmulationStation menu (started when booting RetroPie). From “Installing Additional Emulators” on you can discard the “First Installation” instructions as we will be handling that here.

Step 3: Enable SSH for easy configuration
NOTE: Make sure you have enabled Wi-Fi (through the main Settings page in Emulationstation).
Detailed instructions for enabling SSH can be found HERE.
Press “Start” in the RetroPie Configuration screen. Choose “Quit” and “Quit Emulationstation”. You are now returned to a prompt (command line).
$ sudo raspi-config

And enable SSH through the menu “interfacing options >> SSH >> Enable”. Close the menu.

Get the IP address by typing:

$ ifconfig

and reboot your pi by typing

$ sudo reboot

Step 4: Download OpenMSX, compile and install it
Log-in remotely to your RetroPie using SSH or in the EmulationStation menu, choose “Quit”->”Quit EmulationStation” so you get a command prompt. You are logged in as user “pi”.

Download OpenMSX using wget (mirror):

$ wget https://github.com/openMSX/openMSX/releases/download/RELEASE_0_14_0/openmsx-0.14.0.tar.gz

unzip it:

$ tar -zxvf openmsx-0.14.0.tar.gz

And execute the following commands to be able to compile it and install it:

$ sudo su –
# cd /etc/apt/
# rm -f sources.list
# wget http://bjorn.kuiper.nu/upload/retropie/sources.list
# apt-get -y update
# apt-get -y build-dep openmsx
# exit
$ cd openmsx-0.14.0/build/
$ rm -f custom.mk
$ wget http://bjorn.kuiper.nu/upload/retropie/custom.mk
$ cd ..
$ ./configure

NOTE: `make` takes a while. Go do something else.

$ make
$ sudo make install
$ rm openmsx-0.14.0.tar.gz

Step 5: Install system roms
To be able to use different machines, instead of the default cbios, you need to download the systemroms.zip.

$ cd
$ mkdir -p .openMSX/share/systemroms
$ cd .openMSX/share/
$ wget http://bjorn.kuiper.nu/upload/retropie/systemroms.zip
$ unzip systemroms.zip
$ rm systemroms.zip
$ cd

Step 6: Add MSX to RetroPie / EmulationStation

$ mkdir -p /opt/retropie/configs/msx/
$ cd /opt/retropie/configs/msx/
$ wget http://bjorn.kuiper.nu/upload/retropie/emulators.cfg

$ sudo su –

# cd /etc/emulationstation/
# vi es_systems.cfg

add the following at the bottom of the file before the closing tag.

    <system>
    <name>msx</name>
    <fullname>MSX</fullname>
    <path>/home/pi/RetroPie/roms/msx</path>
    <extension>.rom .ROM .mx2 .MX2 .mx1 .MX1</extension>
    <command>/opt/retropie/emulators/openmsx/bin/openmsx -cart %ROM%</command>
    <platform>msx</platform>
    <theme>msx</theme>
    </system>

Save and quit VI.

Step 7: copy games onto the RetroPie machine

The MSX emulator is not shown in the Emulationstation screen until you actual add some games in the predefined roms folder.

Create the folder
$ mkdir -p /home/pi/RetroPie/roms/msx/

and copy the ROM files into that folder. You can create subfolders as well.

If you have SSH enabled you can use SFTP to copy the ROMs onto your RetroPie. On Windows you could use the FileZilla client to accomplish this.

You can just restart the Emulationstation to show the new games you have added. You don’t need to reboot the system.

Games can be found here:
http://www.msxabandonware.com/en/contentsby/b/1/1
http://www.msxarchive.nl/pub/msx/games/roms/
http://www.planetemu.net/roms/msx-various-rom?page=D

Nice to have: Classic boot screen
By default OpenMSX boots with CBIOS, an opensource MSX emulator, but the systemroms.zip we installed contains a lot of different machines.

For people with a windows keyboard, open the menu by pressing the “Menu-key” on your keyboard (it is between the Altr Gr and Ctrl key on the right). You can also navigate your mouse to the top left corner of the screen and click menu. go to “Hardware-Change Machine” and select a different machine, for example”: Philips NMS 8225. Make it your default by going back the menu and “Hardware-Set Current Machine as Default”.

resources:
(1) https://www.processorstore.nl/product/753520/raspberry-pi-3-model-b-essentials-kit.html
(2) https://retropie.org.uk/docs/First-Installation/

Standing on ye shoulders of Giants. Big thanks go to:
https://www.msxinfo.net/2017/01/20/install-openmsx-on-retropie-with-xbox360-or-picade-controller/
https://www.raspberrypi.org/forums/viewtopic.php?t=31277

DevSummit presentation: Lessons learned: Three years of ArcGIS Runtime for WPF

Thank you for attending my presentation. It should be available through the Esri Website sometime in the near future. In the meantime you can get the slides and pseudo code examples from here.

THE PRESENTATION (.pdf) (12MB)

Date: Wednesday March 12th
Time: 2:30pm – 3:00pm
Location: Mesquite B

Lessons learned: Three years of ArcGIS Runtime for WPF
This presentation will focus on lessons learned with regards to ArcGIS Runtime for WPF. We have been using ArcGIS Runtime for WPF in two different projects that span nearly three years, starting with ArcGIS Runtime for WPF BETA 2 release. During the presentation we will talk about: 1) the current benefits and limitations of ArcGIS Runtime for WPF; 2) the different tools we have developed to improve usage – including a layer manager, saving and retrieving layers from our own database instead of SDE, custom attributes pane and identify tool, setting rendering properties on layers; 3) tips and tricks when working with ArcGIS Runtime.

Pseudo code examples:

C#

using System;

namespace TipsAndTricks.RuntimeFolders
{
    public class Example
    {
        ArcGISRuntime.AppDataPath = Path.Combine(@"C:\Temp\", "AppDataPath");
        ArcGISRuntime.TempPath = Path.Combine(@"C:\Temp\", "TempPath");
    }
}

namespace TipsAndTricks.MEF
{
    public enum GISFramework
    {
        ArcObjects,
        GPK,
    }

    public interface IGIS
    {
        /* placeholder for GISFrameworkAttribute */
        GISFramework Framework { get; }
    }

    public interface IGeodatabase
    {
        void DoGISStuff();
    }

    public class Example
    {
        [ImportMany]
        private Lazy<IGeodatabase, IGIS>[] geodatabase = null;

        public void DoSomeGISStuff()
        {
            IGeodatabase Geo = geodatabase.Get(GISFramework.GPK);
            Geo.DoGISStuff();
        }
    }

    public static class ExtensionMethods
    {
        public static IGeodatabase Get(this Lazy<IGeodatabase, IGIS>[] items, GISFramework framework)
        {
            foreach (var item in items)
            {
                if (item.Metadata.Framework == framework)
                {
                    return item.Value;
                }
            }

            throw new ArgumentOutOfRangeException("No implementation found for: " + framework);
        }
    }
}

namespace TipsAndTricks.RuntimeServices {

    public class Example{

        /// 
        /// Open ArcGIS Runtime Webservice site in browser
        /// 
        /// command parameter
        private void ExecuteOpenArcGISRuntimeWebservice(object parameter)
        {
            if (LocalServer.IsRunning)
            {
                Thread t = new Thread(new ThreadStart(() =>
                {
                    Process.Start(LocalServer.Url);
                }));
                t.Start();
            }
        }

        /// 
        /// Open ArcGIS Runtime Webservice Admin site in browser
        /// 
        /// command parameter
        private void ExecuteOpenArcGISRuntimeWebserviceAdmin(object parameter)
        {
            if (LocalServer.IsRunning)
            {
                Thread t = new Thread(new ThreadStart(() =>
                {
                    Process.Start(LocalServer.Url.Replace("services", "admin"));
                }));
                t.Start();
            }
        }
    }
}

Python

# Tips and Tricks / Python exception handling

def handleExceptions(e):
    msgs = arcpy.GetMessages(2)
    arcpy.AddError(msgs)
    arcpy.AddError(e.message)
    arcpy.AddMessage(" ! Exception Message: " + e.message)
    print " ! Exception Message: " + e.message
    sys.exit()
    return

def main():
    fileToDelete = arcpy.GetParameterAsText(0)

    arcpy.AddMessage(" + fileToDelete" + str(fileToDelete))

    try:
        arcpy.Delete_management(fileToDelete)
    except Exception as e:
        handleExceptions(e)

    arcpy.AddMessage(" = Finished")

if __name__ == '__main__':

    main()

Lego and Playmobil professional cycling sets

While browsing around the web I came across a great Tour de France Team Telekom Lego set released in 2000. It is called the “1199 Race Cyclists and Winners’ Podium” and was part of promotional package of Team Telekom.

After posting about this on Twitter I soon discovered that there are all kinds of Lego and Playmobil cycling sets.

First of all, José Been (@TourDeJose) pointed out that this was exactly part of a set of four. It seems that 1196, 1197, 1198 and 1199 are all part of a Team Telekom promotional package.

Then Ron Kleijnen (@RonKleijnen) pointed out that there is also a Playmobil collection. Ron pointed me to #4994 after which is quickly discovered that both #4994 and #4995 are part of a Team Telekom promotional set.

Mark Young (@miyoung9999) pointed me to a special Vuelta Playmobil set with number #3090.

There is also a couple of older sets from Playmobil with cyclists, #3846 has just a single cyclist, #3847 has a motor TV crew and #3849 has three cyclist with a podium.

Start collecting! 🙂

Update:
You can even order personalized Lego cycling figures, Chris Froome and Team Sky and/or the Paralympics GB squad on minifigs.me.

All the different Lego and Playmobil items are visible below:

Playmobil #4995

Playmobil #3090

Playmobil #3846

Playmobil #3847

Playmobil #3849

The origin of 0-day (zero-day) in hacking (etymology of zero-day)

This post is a result of a tweet [1] by Space Rogue.

It seems that only a few [2] have tried to capture the origin of the word 0-day in hacking and are wrong.

The term 0-day comes originally from the Warez scene [3]:

“0-day (pronounced as zero day) – This refers to any copyrighted work that has been released the same day as the original product, or sometimes even before.[6] It is considered a mark of skill among warez distro groups to crack and distribute a program on the same day of its commercial release.”

Somewhere around the late 90’s it was picked up by the hacking scene.

Wikipedia explains zero-day attacks as following [4]:

A zero-day (or zero-hour or day zero) attack or threat is an attack that exploits a previously unknown vulnerability in a computer application, meaning that the attack occurs on “day zero” of awareness of the vulnerability.[1] This means that the developers have had zero days to address and patch the vulnerability. Zero-day exploits (the software and/or strategies that use a security hole to carry out a successful attack) are used or shared by attackers before the developer of the target software knows about the vulnerability.

The earliest recorded mentioning of 0-day, related to the hacking scene, so far is an e-zine of 1998 called CRH [5], (thanks to @bill_e_ghote for finding this one).

Other references to 0-day in 1998 include a post on BugTraq by Ken Williams [6] and the Line-noise section of Phrack 53 [7].

Let me know if you have found a reference to 0-day (in hacking) before January 31st, 1998. A good place to start looking might be newsgroups, but I have been unlucky so far.

Thanks go to
Space Rogue, twitter: @spacerog website: http://www.spacerogue.net
Bill E. Ghote, Twitter: @bill_e_ghote website: http://scrapeghote.blogspot.com

References:
[1] https://twitter.com/spacerog/statuses/387677286385733632 by @spacerog on October 8, 2013.
[2] http://spiresecurity.com/?p=576 – “Zero Day” Terminology by Pete Lindstrom on July 27, 2005.
[3] http://en.wikipedia.org/wiki/Warez
[4] http://en.wikipedia.org/wiki/Zero-day_attack
[5] http://web.textfiles.com/ezines/CRH/crh007.txt – 7th edition of CRH E-zine published on January 31st, 1998
[6] http://www.shmoo.com/mail/bugtraq/oct98/msg00027.html – Bugtraq October 5th, 1998
[7] http://www.textfiles.com/magazines/PHRACK/PHRACK53 – Phrack 53 July 8th, 1998

PictureMarkerSymbols for offline use

Hi,

With the help from Esri I was able to download the existing PictureMarkerSymbols that are available from their website, as shown here

http://developers.arcgis.com/en/javascript/samples/portal_symbols/index.html

You can download the ‘zip’ (tar.gz) with all symbols from this location

http://bjorn.kuiper.nu/upload/blog/symbols.tar.gz

Enjoy!

DevSummit Presentation on ArcGIS Runtime WPF SDK (BETA 2)

My DevSummit presentation is online!

Starting with ArcGIS Runtime SDK for WPF

ArcGIS Runtime is the new lightweight easy to deploy cross platform GIS framework to host your maps, Geoprocessing services and other services. As of this writing, the ArcGIS Runtime SDK for WPF is in BETA 2, but the future for ArcGIS Runtime is bright and it will probably play a big role in stand-alone client applications in the near future. We have been using the ArcGIS Runtime SDK for WPF since November 2011 (Beta 1), and are one of the first groups to actively use it in a project to move an existing Esri-based Win-Forms application to the new Runtime. In this presentation, you will learn how to get started with ArcGIS Runtime and see what we have learned so far. Topics covered include:

  • A (very) short introduction into the ArcGIS Runtime and how to obtain ArcGIS Runtime
  • How the ArcGIS Runtime works (behind the scenes)
  • Debugging approach
  • Demonstrate an application using ArcGIS Runtime
  • Executing a Geoprocessing Task (hosted within a GP package) and displaying the results
  • Current known limitations (due to BETA status)
  • Tips and Tricks

The presentation can be viewed on the Esri website.

Source-code

A big part of the presentation is still valid on the latest release of the ArcGIS Runtime SDK for WPF, which is currently Prerelease.

ESRI DevSummit presentation from Bjorn Kuiper on Vimeo.

WP7: Tweetsharp and AgFx

Hello world!

WP7: Northern Lights WP7 Toolkit v0.0.1

In the previous months I posted some code examples that I used in my own Windows Phone applications.

After discovering some minor bugs and speed improvements I decided to put them into a toolkit library so they are easier to maintain.

I added the following posts / code examples into the toolkit:

The toolkit is called Northern Lights and hosted on codeplex. The project page is available HERE.

I’m looking forward on feedback and I will try to keep on updating the toolkit with new features while I keep developing my Windows Phone apps.

UPDATE: Currently (12/6/2011) Northern Lights is already at version v0.0.8. Make sure you check the codeplex homepage occasionally or use NuGet to get the latest updates.

WP7: LittleWatson Extended; Error reporting to HTTP endpoint

UPDATE 7 NOV 2011: Have a look at this post: WP7: Northern Lights WP7 Toolkit v0.0.1 for the latest version of this code example.

In the previous post I already mentioned the great talk of Jeff Wilcox on TechEd Australia (video here).

In his talk he mentions LittleWatson, a supporting class that Andy Pennell wrote. This class enables you to catch unhandled exceptions and let the user report these application errors to you, the developer.

A big downside of this class, in my opinion, is that it needs the user to send the report. I therefore took it up me to extend the class and add the possibility to automatically send the error report to an HTTP endpoint.

Here are the classes.

The main class.

LittleWatsonManager.c

namespace MyApp.Utils
{
    using System;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Net;
    using System.Text;

    /// 
    /// LittleWatsonManager class.
    /// 
    /// 
    /// Text to user: Send application error reports automatically and anonymously to southernsun to help us improve the application.
    /// 
    public class LittleWatsonManager
    {
        #region Fields
        private static readonly LittleWatsonManager instance = new LittleWatsonManager();
        private const string Filename = "LittleWatson.txt";
        private const string SettingsFilename = "LittleWatsonSettings.txt";
        private bool allowAnonymousHttpReporting = true;
        #endregion

        #region Constructor
        /// 
        /// Initializes static members of the LittleWatsonManager class.
        /// 
        static LittleWatsonManager()
        {
        }

        /// 
        /// Prevents a default instance of the LittleWatsonManager class from being created.
        /// 
        private LittleWatsonManager()
        {
            this.allowAnonymousHttpReporting = this.GetSetting();
        }
        #endregion

        #region Properties
        /// 
        /// Gets DataManager instance.
        /// 
        public static LittleWatsonManager Instance
        {
            get
            {
                return LittleWatsonManager.instance;
            }
        }

        /// 
        /// Gets or sets a value indicating whether error reports are allowed to send anonymously to a http endpoint.
        /// 
        public bool AllowAnonymousHttpReporting
        {
            get
            {
                return this.allowAnonymousHttpReporting;
            }

            set
            {
                this.allowAnonymousHttpReporting = value;
                this.SetSetting(this.allowAnonymousHttpReporting);
            }
        }
        #endregion

        #region Public Methods
        /// 
        /// Report exception.
        /// 
        /// The exception to report.
        public static void SaveExceptionForReporting(Exception ex)
        {
            if (ex == null)
            {
                return;
            }

            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (TextWriter output = new StreamWriter(store.OpenFile(Filename, FileMode.OpenOrCreate & FileMode.Truncate)))
                    {
                        output.WriteLine(Serializer.WriteFromObject(new ExceptionContainer() { Message = ex.Message, StackTrace = ex.StackTrace }));
                    }
                }
            }
            catch
            {
            }
        }

        /// 
        /// Check for previous logged exception.
        /// 
        /// Return the exception if found.
        public static ExceptionContainer GetPreviousException()
        {
            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    if (store.FileExists(Filename))
                    {
                        using (TextReader reader = new StreamReader(store.OpenFile(Filename, FileMode.Open, FileAccess.Read, FileShare.None)))
                        {
                            string data = reader.ReadToEnd();

                            try
                            {
                                return Serializer.ReadToObject(data);
                            }
                            catch
                            {
                            }
                        }

                        store.DeleteFile(Filename);
                    }
                }
            }
            catch
            {
            }

            return null;
        }

        /// 
        /// Send error report (exception) to HTTP endpoint.
        /// 
        /// Exception to send.
        public void SendExceptionToHttpEndpoint(ExceptionContainer exception)
        {
            if (!this.AllowAnonymousHttpReporting)
            {
                return;
            }

            try
            {
                string uri = "http://www.yourwebsite.com/data/post.php";

                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
                webRequest.Method = "POST";
                webRequest.ContentType = "application/x-www-form-urlencoded";

                webRequest.BeginGetRequestStream(
                    r =>
                    {
                        try
                        {
                            HttpWebRequest request1 = (HttpWebRequest)r.AsyncState;
                            Stream postStream = request1.EndGetRequestStream(r);

                            string info = string.Format("{0}{1}{2}", exception.Message, Environment.NewLine, exception.StackTrace);

                            string postData = "&exception=" + HttpUtility.UrlEncode(info);
                            byte[] byteArray = Encoding.UTF8.GetBytes(postData);

                            postStream.Write(byteArray, 0, byteArray.Length);
                            postStream.Close();

                            request1.BeginGetResponse(
                                s =>
                                {
                                    try
                                    {
                                        HttpWebRequest request2 = (HttpWebRequest)s.AsyncState;
                                        HttpWebResponse response = (HttpWebResponse)request2.EndGetResponse(s);

                                        Stream streamResponse = response.GetResponseStream();
                                        StreamReader streamReader = new StreamReader(streamResponse);
                                        string response2 = streamReader.ReadToEnd();
                                        streamResponse.Close();
                                        streamReader.Close();
                                        response.Close();
                                    }
                                    catch
                                    {
                                    }
                                },
                            request1);
                        }
                        catch
                        {
                        }
                    },
                webRequest);
            }
            catch
            {
            }
        }
        #endregion

        #region Private Methods
        private bool GetSetting()
        {
            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (TextReader reader = new StreamReader(store.OpenFile(SettingsFilename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None)))
                    {
                        string content = reader.ReadToEnd();

                        if (!string.IsNullOrEmpty(content))
                        {
                            try
                            {
                                return Serializer.ReadToObject(content);
                            }
                            catch
                            {
                            }
                        }
                    }
                }
            }
            catch
            {
            }

            return true;
        }

        private void SetSetting(bool value)
        {
            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (TextWriter output = new StreamWriter(store.OpenFile(SettingsFilename, FileMode.OpenOrCreate & FileMode.Truncate, FileAccess.Write, FileShare.None)))
                    {
                        try
                        {
                            output.WriteLine(Serializer.WriteFromObject(value));
                        }
                        catch
                        {
                        }
                    }
                }
            }
            catch
            {
            }
        }
        #endregion
    }
}

We make our own ExceptionContainer that can be serialized.

ExceptionContainer.c

namespace MyApp.Utils
{
    using System;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Net;
    using System.Text;

    /// 
    /// ExceptionContainer class.
    /// 
    public class ExceptionContainer
    {
        /// 
        /// Gets or sets the message.
        /// 
        public string Message { get; set; }

        /// 
        /// Gets or sets the stacktrace.
        /// 
        public string StackTrace { get; set; }
    }
}

The supporting Serializer class.

Serializer.c

namespace MyApp.Utils
{
    using System.IO;
    using System.Runtime.Serialization.Json;
    using System.Text;

    /// 
    /// Serializer class.
    /// 
    public class Serializer
    {
        /// 
        /// Serialize object.
        /// 
        /// The Object type.
        /// The object to serialize.
        /// The serialized object.
        public static string WriteFromObject(T obj)
        {
            MemoryStream ms = new MemoryStream();
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            ser.WriteObject(ms, obj);
            byte[] json = ms.ToArray();
            ms.Close();
            return Encoding.UTF8.GetString(json, 0, json.Length);
        }

        /// 
        /// Deserialize object.
        /// 
        /// The object type.
        /// The serialized object.
        /// The deserialized object.
        public static T ReadToObject(string json)
        {
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json));
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            T obj = (T)ser.ReadObject(ms);
            ms.Close();
            return obj;
        }
    }
}

The HTTP endpoint that processes the error report, a PHP file.


You hook the manager with the UnhandledExceptionHandler of your App.xaml.cs

LittleWatsonManager.SaveExceptionForReporting(e.ExceptionObject as Exception);

And then add some code in your MainPage to report previous errors to your website:

            ExceptionContainer exception = LittleWatsonManager.GetPreviousException();

            if (exception != null)
            {
                if (LittleWatsonManager.Instance.AllowAnonymousHttpReporting)
                {
                    LittleWatsonManager.Instance.SendExceptionToHttpEndpoint(exception);
                }
                else
                {
                    // show popup.
                    this.notification.Show("Unhandled exception found", new SolidColorBrush(Colors.Red), null);
                }
            }

To summary what happens. An unhanled exception happens and is caught by your apps unhandled exception handler which contains the SaveExceptionForReporting() method to save the exception. The next time the user starts your application the code in your MainPage.xaml.cs will check if there was any exception that needs to be reporting. Depending on the settings this exception will be pushed to a HTTP endpoint as a POST request with a variable ‘exception’ that contains the original exception message and stacktrace. In my example the message is then e-mailed to my personal e-mailaddress. If the user opt-outs on this, you can still show him a popup and ask him to send the error report. You should allow the user to change these settings. This can achieved by changing the AllowAnonymousHttpReporting boolean of the manager.

You should make a settings page that lets the user select the option to “Send application error reports automatically and anonymously to ‘companyname’ to help us improve the application”.

So that’s it. Hope you like it!

Let me know if you have any questions.

WP7: Notify user of new Application Version

UPDATE 7 NOV 2011: Have a look at this post: WP7: Northern Lights WP7 Toolkit v0.0.1 for the latest version of this code example.

Inspired by the great talk of Jeff Wilcox on TechEd Australia (video here) I dediced to implement a Application Version checker for my application before I publish my first version to the marketplace.

Jeff Wilcox is the maker of 4Th & Mayor, a successful Windows Phone 7 application. In his talk on TechEd Australia he mentioned some tips&tricks for future application developers. One of those tips is to build a version checker in your app so you can notify the users when there is a new version available for your app.

He has a lot of users that are running an old version of his app and he has no way to reach out to them and inform them that there is a new version with a lot of new features.

The following code should solve this problem.

I implemented the AppVersionManager class which gets the latest version number from a text-file online and compares this with the version number stored in your WMAppManifest.xml.

AppVersionManager.c

namespace MyApp.Utils
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Net;
    using System.Threading;

    /// 
    /// AppVersionManager class.
    /// 
    public class AppVersionManager
    {
        #region Fields
        private static string uri = "http://www.myapp.com/data/appversion.txt";
        #endregion

        #region Public methods
        /// 
        /// Check for new version of app and execute action when new version is available.
        /// 
        /// The action to execute when there is a new version available.
        public static void CheckForNewVersion(Action action)
        {
            BackgroundWorker bgw = new BackgroundWorker();
            bgw.DoWork += (sender, e) =>
            {
                Version appVersion = GetVersion();

                DoWebRequest(AppVersionManager.uri, appVersion, action);
            };

            bgw.RunWorkerAsync();
        }
        #endregion

        #region Private methods
        private static Version GetVersion()
        {
            string version = StateManager.Get("AppVersion");

            if (string.IsNullOrEmpty(version))
            {
                version = Utils.General.GetVersion();
                StateManager.Set("AppVersion", version);
            }

            try
            {
                return new Version(version);
            }
            catch
            {
            }

            return default(Version);
        }

        private static void DoWebRequest(string uri, Version appVersion, Action action)
        {
            string id = "appversionrequest";

            if (action == null)
            {
              return;
            }

            Timer t = null;
            int timeout = 60; // in seconds

            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
                request.Accept = "*/*";
                request.AllowAutoRedirect = true;
                
                // disable caching.
                request.Headers["Cache-Control"] = "no-cache";
                request.Headers["Pragma"] = "no-cache";

                t = new Timer(
                    state =>
                    {
                        if (string.Compare(state.ToString(), id, StringComparison.InvariantCultureIgnoreCase) == 0)
                        {
                            Debug.WriteLine(string.Format("Timeout reached for connection [{0}], aborting download.", id));

                            request.Abort();
                            t.Dispose();
                        }
                    },
                    id,
                    timeout * 1000,
                    0);

                request.BeginGetResponse(
                    r =>
                    {
                        try
                        {
                            if (t != null)
                            {
                                t.Dispose();
                            }

                            var httpRequest = (HttpWebRequest)r.AsyncState;
                            var httpResponse = (HttpWebResponse)httpRequest.EndGetResponse(r);

                            if (httpResponse.StatusCode == HttpStatusCode.OK)
                            {
                                using (StreamReader reader = new StreamReader(httpResponse.GetResponseStream()))
                                {
                                    string response = reader.ReadToEnd();

                                    try
                                    {
                                        Version currentVersion = new Version(response);

                                        if (appVersion < currentVersion)
                                        {
                                            action();
                                        }
                                    }
                                    catch
                                    {
                                    }
                                }
                            }
                            else
                            {
                                Debug.WriteLine(string.Format("Error occured accessing endpoint: {0} [{1}]", httpResponse.StatusCode, uri));
                            }
                        }
                        catch
                        {
                            Debug.WriteLine(string.Format("Error occured accessing endpoint: [{0}]", uri));
                        }
                    },
                    request);
            }
            catch
            {
            }
        }

        #endregion
    }
}

the Utils.General.GetVersion() implementation

        public static string GetVersion()
        {
            Uri manifest = new Uri("WMAppManifest.xml", UriKind.Relative);
            var si = Application.GetResourceStream(manifest);
            if (si != null)
            {
                using (StreamReader sr = new StreamReader(si.Stream))
                {
                    bool haveApp = false;
                    while (!sr.EndOfStream)
                    {
                        string line = sr.ReadLine();
                        if (!haveApp)
                        {
                            int i = line.IndexOf("AppPlatformVersion=\"", StringComparison.InvariantCulture);
                            if (i >= 0)
                            {
                                haveApp = true;
                                line = line.Substring(i + 20);
                                int z = line.IndexOf("\"");
                                if (z >= 0)
                                {
                                    // if you're interested in the app plat version at all                      
                                    // AppPlatformVersion = line.Substring(0, z);                    
                                }
                            }
                        }

                        int y = line.IndexOf("Version=\"", StringComparison.InvariantCulture);
                        if (y >= 0)
                        {
                            int z = line.IndexOf("\"", y + 9, StringComparison.InvariantCulture);
                            if (z >= 0)
                            {
                                // We have the version, no need to read on.                    
                                return line.Substring(y + 9, z - y - 9);
                            }
                        }
                    }
                }
            }

            return "Unknown";
        }

The contents of the appversion.txt file

1.1.0.0

So know you can call AppVersionManager from your MainPage.xaml to check for a new version and execute whatever you want to notify the user of your new version of the application.

            AppVersionManager.CheckForNewVersion(() =>
            {
                // notify user of new version.
            });

Probably the best way is to show a popup and ask the user if he wants to upgrade and then navigate him to the MarketPlace.

Alright, that's all. I hope you like it! Feedback is welcome.

UPDATE: added 'if (action == null)' in the top of DoWebRequest method to skip webrequest when there is no action specified. Thanks to @peSHIr.