31 December 2010

Proxying Server from Apache on Synology

How to redirect a part of your web site to another server on your LAN:

... On your NAS: at the bottom of /usr/syno/apache/conf/httpd.conf-user

ProxyRequests Off
<proxy *>
  Order deny,allow
  Allow from all
</proxy>
ProxyPass /redmine http://192.168.0.78/redmine
ProxyPassReverse /redmine http://192.168.0.78/redmine
<location redmine>
  AuthName "Protected Access"
  AuthType Basic
  AuthUserFile /var/services/web/hg/.htpasswd
  require valid-user
</location>



It tells Apache to redirect all /redmine/* access to another server (this subdirectory is protected by password in this case).

And... That's all :)

Virtual machine as Windows service

A follow up from
Synology as Web Gateway (part one)
Once you have a virtual server up and running in Virtualbox, the question of starting it automatically in the background may be solved by the use of ServiceMan.

ServiceMan is a small win32 app i wrote as Virtualbox (3.xx) is not natively service-able. It features execution of a set of cmd lines, and on-the-fly environment variables setting. Both are needed to start a virtualbox machine (plus is able to launch many apps in one go, and support internal variables).

You need to type a cmd line to register ServiceMan as a service (it will be executed at host start).
ServiceMan -i [ServiceName]

ServiceMan will get all information from a old-fashioned ServiceMan.ini located in the executable directory. Here is mine in this case:



[General]
;this shows in services control panel
Description=ServiceMan (a virtual machine using virtualbox)

;
;From now, each section is considered to be triggered sequentially
;at host startup and shutdown
;
;We're launching a virtual server in this one
[VirtualUbuntu]

;
;Aliases : allow to define internal variables you'll be reusing later in config file
;here i'm using a portable edition of virtualbox, and need to set some paths
;
Aliases=VboxUserHome=D:\Machines\Portable-VirtualBox\data\.VirtualBox;VboxDir=D:\Machines\Portable-VirtualBox\app64

;A more conventional virtual box installation should use this
;Aliases=VboxUserHome=C:\Documents and Settings\[User Name]\.VirtualBox;VboxDir=C:\Program Files\Oracle\VirtualBox

;
;Environment variables to add to your configuration when executing cmd lines
;
EnvVars=VBOX_USER_HOME=%VboxUserHome%;VBOX_INSTALL_PATH=%VBoxDir%

;
;Working directory
;
WorkingDir=%VboxDir%

;
;we can redefine envvars for that particular service event
;with StartupEnvVars=VBOX_USER_HOME=%VboxUserHome%;VBOX_INSTALL_PATH=%VBoxDir%
;following are the command executed sequentially. It waits 1000s by default
;
;doing this should force vboxsvc to read new env vars
StartupCmd1="%VboxDir%\vboxsvc.exe" /reregserver
;it's possible to define a pause between commands (default is 1000 ms)
;StartupPause1=1000
StartupCmd2="%VboxDir%\vboxmanage.exe" list systemproperties
StartupCmd3="%VboxDir%\vboxmanage.exe" list vms
StartupCmd4="%VboxDir%\vboxheadless.exe" -startvm VirtualLinux

;
;and shutdown event: again, if we need to redefine things...
;ShutdownEnvVars=VBOX_USER_HOME=%VboxUserHome%;VBOX_INSTALL_PATH=%VBoxDir%
;ShutdownWorkingDir=%VboxDir%
;
ShutdownCmd1="%VboxDir%\vboxmanage.exe" controlvm VirtualLinux savestate

;
;you may have more than one app to launch as a service: open another section
;[AnotherSetOfCommand]
;etc, etc
;



If you need to start and stop for testing, use the service control panel or the cmd line -s / -k option.

Note that it generates a session exec log in ServiceMan directory (which, therefore, should be writable).

Download ServiceMan

24 November 2010

Synology as Web gateway (part1)

RedMine is a great tool for developpers: unfortunately it does not perform well on my synology DS-107+ (for various reasons i don't have time to inspect, low memory is probably a first answer)...

Anyway, as i'm having a-lot-of (probably too-much) machines available @ home: i choose to dedicate some horsepower to run a minimalistic virtual linux machine with Apache and RedMine (note that Bitnami is offering such packages ready for download).

So my idea was to use the Synology as a gateway between the internet and my home LAN, mainly to centralize security settings, and access point uniqueness.

Many topics are covered in this recipe:

1) Installing a virtual ubuntu server running redmine
2) Running a virtual machine as a service under Windows 7
3) Binding the Synology web frontend and a virtual server

Creating the virtual machine : ubuntu+redmine
Get VirtualBox, http://www.virtualbox.org/ . Eventually look for a portable edition.
    Create a new virtual machine: Ubuntu type, i dedicated 20gb of expandable disk space and 512Mo memory (but 5Go and 256Mo should be fine). Use Bridged Network Access for network connections (it should allow the virtual machine to have its own static IP address on your LAN).
      Note: to move a virtual server from one machine to another: ensure that you're using the same netword card between virtualbox installations: type of network card and MAC address must be the same. (note that if network is not working properly on virtualized linux, ensure that /etc/udev/rules.d/70-persistent-net.rules doesn't contain deprecated MAC entries).
      Grab Ubuntu server ISO, attach it to your virtua machine and perform the (blazingly fast) install: select Apache and MySQL components
        • Once connected to the console, you might notice a sluggish refresh:
        howto blackList vga mode :
        sudo nano /etc/modprobe.d/blacklist-framebuffer.conf
        add the following line:
        blacklist vga16fb
        Note: you can enable and use remote access to a virtualbox machine...
        Enforcing static IP use for the virtual Ubuntu Server (192.168.0.78 in my case)

            sudo vi /etc/network/interfaces
            change
            auto eth0
            iface eth0 inet dhcp
            into
            auto eth0
            iface eth0 inet static
            address 192.168.0.78
            netmask 255.255.255.0
            network 192.168.0.0
            broadcast 192.168.0.255
            gateway 192.168.0.254
            store your dns info via
            sudo vi /etc/resolv.conf
            you should read/add something like this
            nameserver 208.67.220.220
            nameserver 208.67.222.222
             and finally
            sudo /etc/init.d/networking restart
            Install VirtualBox extensions on ubuntu servers
              apt-get install build-essential linux-headers-`uname -r`
              mount /dev/cdrom /media/cdrom
              cd /media/cdrom
              ./VBoxLinuxAdditions-x86.run
              Perform Ubuntu system update:
                sudo apt-get update
                sudo apt-get install ruby
                sudo apt-get install rubygems
                Install Ruby requirements for RedMine
                  sudo gem install
                  gem install rails -v 2.3.5 -no-doc -no-ri
                  gem install rack -v 1.0.1 -no-doc -no-ri
                  Plug Ruby with MySQL, using an adapter:
                    cd /tmp
                    check latest version http://github.com/tmtm/ruby-mysql/downloads
                    wget http://github.com/downloads/tmtm/ruby-mysql/ruby-mysql-2.9.3-beta.tar.gz
                    tar -xzvf ruby-mysql-2.9.3-beta.tar.gz
                    cd ruby-mysql-2.9.3-beta/
                    ruby setup.rb
                    Eventually install PHPMyAdmin
                      sudo wget http://sourceforge.net/projects/phpmyadmin/files%2FphpMyAdmin%2F3.3.7%2FphpMyAdmin-3.3.7-all-languages.tar.gz
                      sudo tar -xzvf 
                      sudo mv /apps/phpMyAdmin
                      On Ubuntu Server: symbolic links in /etc/apache2/sites-enabled point to 'available' web sites. Use the a2ensite (Apache2 Enable Site) command to create such symbolic links, like this: sudo a2ensite mynewsite where your site configuration file is /etc/apache2/sites-available/mynewsite . Similarly, the a2dissite utility is used to disable sites.
                      Enabling your PhpMyAdmin directory as a site
                        cd /etc/apache2/sites-available
                        ln /phpmyadmin /apps/phpmyadmin-3.3.7
                        sudo a2ensite phpmyadmin
                        /etc/init.d/apache2 reload
                        browse to address/phpmyadmin
                        login/root from installation credentials
                        sometimes php fails when triggered (security problems), edit
                        sudo nano /etc/php5/apache2/php.ini
                        and edit open_basedir = to add your own '/apps/' (which allow php execution from that directory).
                        Install Mercurial
                          sudo apt-get install mercurial
                          Download and configure Redmine (http://www.redmine.org/wiki/redmine/RedmineInstall)
                            chown -R www-data:www-data /apps/
                            cd /volume1/apps
                            wget http://rubyforge.org/frs/download.php/72201/redmine-1.0.1.tar.gz
                            tar -xzvf redmine-1.0.1.tar.gz

                            mv redmine-1.0.1 redmine
                            cd redmine/config
                            cp database.yml.sample database.yml
                            nano /config/database.yml

                            production
                            adapter: mysql
                            username: redmine
                            password: xxxxxxxx

                            sudo apt-get install libopenssl-ruby1.8
                            sudo apt-get install rake

                            cd redmine

                            RAILS_ENV=production rake config/initializers/session_store.rb
                            RAILS_ENV=production rake db:migrate
                            RAILS_ENV=production rake redmine:load_default_data
                            ruby script/server -p production

                            in redmine login/password: admin admin

                            Install passenger (module enabling execution of ruby servicing in Apache)
                              sudo apt-get install libapache2-mod-passenger
                              load module in sites apache config scripts if not included by default
                              LoadModule passenger_module modules/mod_passenger.so

                              Install RedMine site under Apache (http://www.he1ix.org/?p=572)
                                cd /etc/apache2/sites-available
                                Alias /redmine /apps/redmine-1.0.1/public
                                sudo a2ensite redmine
                                sudo /etc/init.d/apache2 reload
                                sudo chown -R www-data.www-data files log tmp public/plugin_assets
                                sudo a2enmod rewrite
                                cd /apps/redmine-1.0.1/public
                                copy dispatch.cgi.example dispatch.cgi
                                If you have problem with /tmp/mysql.sock (Redmine having some problems to access mysql: can't find /tmp/mysql.sock):
                                you cant use sudo  ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock (file is not persistent), but you may specify the ubuntu mysql socket path in redmine config: edit /redmine/config/database.yml
                                production:
                                  adapter: mysql
                                  socket: /var/run/mysqld/mysqld.sock
                                
                                
                                Enabling NFS services on your synology: to allow your LAN machine to access a repository on the synology
                                  synoservice --list
                                  synoservice --enable nfs
                                  nano /etc/exports

                                  In 'exports' file, add the following line
                                  /directory/ ip.of.NFS.client(ro,root_squash,no_subtree_check)
                                  (* as 'ip.of.NFS.client', means 'for all computers on LAN')

                                  Save the 'exports' file

                                  Check if the file /var/lib/nfs/rmtab does exist, if not, run the following command
                                  touch /var/lib/nfs/rmtab

                                  Now perform
                                  cd / /usr/sbin/exportfs -a

                                  Testing: look at /var/log/messages to check whether the settings are ok with your Synology product

                                  On the Ubuntu Server side (read this):

                                  sudo apt-get install nfs-kernel-server
                                  sudo mkdir /apps/mnt
                                  sudo mount 192.168.0.77:/volume1/repos

                                  sudo vi /etc/fstab
                                  to add
                                  192.168.0.77:/volume1/repos /nfsmount nfs
                                  That's it for the virtual ubuntu server install !

                                    26 September 2010

                                    RedMine (Synology)

                                    RedMine is a great project/content management targeted at developers. I'm personnally using it as a complementary tool alongside Mercurial source code repository (cf previous posts):

                                    And after a terrible fight, i managed to get Redmine running on my Synology through Apache.

                                    First, Redmine is coded with Ruby, and that's partially a problem concerning the webserver to run this Web App. CGI interface is deprecated, and Mongrel, Thin, Unicorn, FastCGI gems (which are the usual suspects :)) are not built for our plateform. After spending some days trying, i resign myself using Webrick (which is not intended for production use, but for development, anyways it's working out of the box).

                                    Using Webrick, our ruby webapp will run standalone, accessible via http port 3000. If you want to use Apache to access it (because you cant access port 3000 from the outside, because you need secured transations) you'll need to set 'reverse proxying' to map one of your Apache web directory (https://diskstation/redmine/ ) to Webrick server.

                                    The most important about this installation is these references
                                    http://wiki.joachimschuster.de/index.php/Install_Ruby_on_Rails_and_Redmine_on_DS210%2B
                                    (choose the 'root install' way)
                                    http://www.vinc3nt.fr/2010/03/installer-redmine-sur-un-synology-ds209ii/
                                    (in french, use a dedicated redmine user which complicates things a little)
                                    http://www.redmine.org/wiki/redmine/RedmineInstall
                                    (Redmine official install howto)
                                    http://www.redmine.org/wiki/1/HowTo_Install_Redmine_in_a_sub-URI
                                    (and finally to connect the running instance with Apache)

                                    To write it again, the steps are:

                                      Setup MySQL (no other backend look usable right now)

                                        Activate MySQL services with Synology Console
                                        Create a 'redmine' database in MySQL (via phpmyadmin)
                                    http://forum.synology.com/wiki/index.php/How_to_manage_the_MySQL_database_using_phpMyAdmin
                                    (unzip phpmyadmin into shared web. copy phpmyadmin\config.sample.inc.php into config.inc.php, and add these ''. then flag it as read only.
                                    go on http://diskstation/phpmyadmin
                                      in privileges:
                                        create user in mysql db
                                        check 'create database for user + all credentials'
                                      Install 'Ruby and the gems'
                                    ipkg install rubygems
                                      Install the right version of Rake and Rails (long process => coffee time):
                                    gem install rails -v 2.3.5
                                    gem install rack -v 1.0.1
                                      Plug Ruby with MySQL, using an adapter:
                                    cd /tmp
                                    check http://github.com/tmtm/ruby-mysql/downloads
                                    wget http://github.com/downloads/tmtm/ruby-mysql/ruby-mysql-2.9.3-beta.tar.gz
                                    tar -xzvf ruby-mysql-2.9.3-beta.tar.gz
                                    cd ruby-mysql-2.9.3-beta/
                                    ruby setup.rb
                                      Download and configure Redmine (http://www.redmine.org/wiki/redmine/RedmineInstall)
                                    mkdir /volume1/apps/
                                    cd /volume1/apps
                                    wget http://rubyforge.org/frs/download.php/72201/redmine-1.0.1.tar.gz
                                    tar -xzvf redmine-1.0.1.tar.gz
                                    mv redmine-1.0.1 redmine
                                    chown -R nobody:users redmine/
                                    cp config/database.yml.sample config/database.yml
                                    nano config/database.yml
                                    (enter MySQL credential) 
                                      Session Key creation &  Database init
                                    RAILS_ENV=production rake config/initializers/session_store.rb
                                    RAILS_ENV=production rake db:migrate
                                      You should be able to launch Redmine from this point, with the command line.
                                    ruby /volume1/apps/redmine/script/server -e production
                                       Create a startup script, daemonizing redmine:
                                    nano /opt/etc/init.d/S97rubyrails.sh
                                    #!/bin/ash
                                      case "$1" in
                                       start)
                                         /opt/bin/ruby /volume1/rubyapps/redmine/script/server webrick -d -e production
                                       ;;
                                       stop)
                                         killall ruby
                                       ;;
                                       restart)
                                         $0 stop
                                         sleep 1
                                         $0 start
                                       ;;
                                       *)
                                         echo “usage: $0 { start | stop | restart}” &>2
                                         exit 1
                                       ;;
                                       esac
                                    don't forget to chmod 755 S97rubyrails.sh
                                      Now, you can access redmine via  http://synology:3000 .
                                    But Let's go one step further by implementing a proxy within Apache. The proxy thing helps if you need access to your redmine setup through the default syno apache frontend: it makes sense when you're enforcing security with passwords (Apache style), or only want to open your 80 port in your firewall.

                                      Finally, Reverse Proxying with Apache:

                                        Add this to /usr/syno/apache/conf/httpd.conf-user
                                    LoadModule proxy_module modules/mod_proxy.so
                                    LoadModule proxy_http_module  modules/mod_proxy_http.so
                                    ProxyRequests Off
                                    ProxyPreserveHost On
                                    ProxyPass /redmine/ http://127.0.0.1:3000/
                                    ProxyPassReverse /redmine/ http://127.0.0.1:3000/
                                    Apache server restart
                                    /usr/syno/etc.defaults/rc.d/S97apache-user.sh restart
                                      And finally the most important part: tell Redmine to prefix every url with a 'redmine/'when generating pages:
                                    Add the following line at the end of your Redmine config/environment.rb
                                    Redmine::Utils::relative_url_root = "/redmine"
                                      Done ! Hopefully. Access to redmine via http://synology.ip/redmine

                                      Final note: after some time on using RedMine, i wouldn't recommend using it on a low-end synology. As an example, my DS-107+ really takes age at updating project wiki pages. In fact, i ended using the NAS as a gateway between the outside, and some other private servers running on my private network... Next post should about this.

                                    20 September 2010

                                    Mercurial on Synology (hg web)



                                    I switched from a single Mercurial repository configuration (cf previous post) to one-per-project-so-multiple-repositories on my Synology.

                                    All my projects would be stored on NAS, located under a specific directory  /volume1/repos , and browsable through internet at http://diskstation/hg

                                    I was using ssh until now but someone suggested an hg web configuration: so, basically we are going to configure the native Apache web server to allow Mercurial transactions *and repositories browsing* via http/https.


                                    This is how i did it, the 'minimal Apache fuss' way, dealing with a dedicated web directory via .htaccess/.htpasswd files: it's far from perfect, security speaking, as it does not follow every Apache recommendations. 
                                    Sorry, but i didn't want to cripple this How-To with too much external considerations.

                                    1) Requirements:
                                    • being able to telnet into your synology box
                                    • being able to ipkg packages
                                    • having mercurial installed
                                    • having nano installed, or mastering vi
                                      (cf previous post)

                                    Note:
                                    • to edit text files, install nano with ipkg install nano
                                    • if you're editing files into windows through shared directories (using pspad ?) dont forget to save files the unix way (CR). Or Apache will fail @ parsing them.
                                    2) Enable Web Service on Synology:
                                          Apache web service should now be 'live', and you should have a new 'web' shared directory in /volume1/web or /var/services/web (both paths are identical).

                                    3) Creating a directory of Repositories (with correct credentials):
                                    • telnet into your diskstation, and type
                                        mkdir /volume1/repos/hg
                                        chown -R nobody:users /volume1/repos
                                      hgweb/apache would have problems to access repos otherwise (Error: abort: HTTP Error 500: Permission denied: .hg/store/lock), as processes are spawned with nobody:users credentials (check this with 'ps', looking at httpd processes group and user).
                                       could be good to create a shared repos/ directory (via syno control panel).
                                    • now create a /volume1/repos/hg/test repository, type :
                                         cd /volume1/repos/hg
                                         hg init test
                                         cd test
                                         nano test.txt (write some and save)
                                         hg add test.txt
                                         hg commit -u yourname -m first
                                    • create a /volume1/web/hg subdirectory
                                          cd /volume1/web
                                          mkdir hg
                                     
                                    4) Time for Script and Config 
                                    • create a .htaccess file to allow script executions in /volume1/web/hg/.htaccess
                                        cd /volume1/web/hg (or cd /var/services/web/hg)
                                        nano .htaccess

                                        (.htaccess is an Apache config file specifying options for this specific directory
                                    • insert this:
                                    AddHandler cgi-script .cgi
                                    Options +FollowSymLinks +ExecCGI

                                    AuthUserFile /var/services/web/hg/.htpasswd
                                    AuthName "Protected Access"
                                    AuthType Basic

                                    Require valid-user
                                      In this directory, Apache is allowed to execute cgi scripts and will protect access with login/passwords. If you don't want a password protected directory, erase all but the two first lines.
                                      Then, to password access /volume1/web/hg/:
                                    • create a .htpasswd file in /volume1/web/hg/ to store login info
                                        cd /volume1/web/hg    

                                        nano .htpasswd
                                      now, get and modify the Mercurial web cgi script (into /volume1/web/hg)
                                    • get mercurial hgweb.cgi from there or via
                                          cd /volume1/web/hg 
                                        wget http://www.selenic.com/repo/hg-stable/raw-file/7cf258b2d0cc/hgweb.cgi 
                                    • rename it into index.cgi
                                          mv hgweb.cgi index.cgi 
                                    • flag this python script as executable, and assign credentials (thx anon)
                                          chmod u+x index.cgi 
                                          chown -R nobody:users index.cgi
                                    • create a symbolic link to your favorite python executable: 
                                          ln /opt/bin/python2.6 /opt/bin/python
                                    • change /volume1/web/hg/index.cgi first line to: 
                                         #!/opt/bin/python
                                    • change in index.cgi (thx Mike):
                                    config = "/var/services/web/hg/hgweb.config"
                                    • create hgweb config file in /volume1/web/hg/hgweb.config
                                         nano /volume1/web/hg/hgweb.config
                                    • write
                                    [collections]
                                    /volume1/repos/hg = /volume1/repos/hg
                                    this config file is parsed by hgweb.cgi (now index.cgi), and will allow it to scan the whole directory to build the repository list. They are other options to play with.
                                    5?) Optionally, error feedbacks from Apache: 
                                    • edit apache config /usr/syno/apache/conf/httpd.conf-user
                                          nano /usr/syno/apache/conf/httpd.conf-user
                                    • to get log informations, change the line
                                          ErrorLog /dev/null
                                        into
                                          ErrorLog /var/log/httpd-error-user.log
                                    • save and restart apache
                                          /usr/syno/etc.defaults/rc.d/S97apache-user.sh restart
                                    • then, to access apache log
                                         cat /var/log/httpd-error-user.log|more

                                    Final notes: 
                                      Use the latter two to pull/push changes from/into other repos.
                                      Mercurial pushes are only allowed through https: it's possible to change this behavior in hgweb.config. Otherwise, dont forget to open your router to port 443 transactions.
                                      And yes it's working: i'm using it every day :)
                                      With Redmine. Next post should be totally about RedMine.

                                    References:




                                    31 May 2010

                                    XML Parsing and Indexing

                                    One of the idea behind GFE ([dje-fe], see previous post) is to be the less intrusive and the most reactive application. A front-end shouldnt need long processes stopping the user about to play, nor any private files written on disk (especially if you want it running from a DVD).
                                    But GFE needs to display informations about currently selected game (from a collection of 10000 items).
                                    Such data exists and is usually available through (big) xml files... They're generated by Mame.exe, or available from the Net (google 'dat file emulator').
                                    Hence the idea to parse like 40 Megs of xml data on the fly (at each run), and index/map the document to avoid keeping the whole file in memory.
                                    After two weeks studying the topic, it's time for conclusions :
                                    1. Microsoft Xml Pull Parser is fast. Written in .Net, but *correctly* written, it's generally a good tool for your xml needs. It's a pull system, easier to manipulate than SAX, and probably faster (look at XmlTextReader for reference). It takes 750 ms to walk 40 megs of data (8800 records) on my computer.
                                    2. Unfortunately you can't bookmark the interesting parts of your document with it. We could rely on the line/character pair this parser is returning but then another text parser should extract text blocks from line/characters pairs. Maybe tricky to implement and we'll lose some horsepower in the process.
                                    3. Note that parsing xml files is easy as long as we don't want document validation or xpath support: state machine only has to handle 10-12 token types, c# is silently handling various character format.
                                    So what ? Why not writing a dedicated indexing-xml-pull-parser ?
                                    Some results after a dozen hour of coding, a custom pull parsing implementation:
                                    1. It's now as fast as MS XmlTextReader:  first version wasn't :) too much function calls is hitting very hard c# performance (8 times slower than the original !), solution is to pack the whole state machine into a single function: now it's easily indexing  50 Megs of xml per second.
                                    2. It's able to give file stream position information about each element start and end: during some initialization pass, an indexer stores each record key and location values in a dictionnary, for further fast access.
                                    3. When GFE needs a particular record info, the dictionnary is requested for the location of the fragment which is read from huge file, and loaded  into memory to be thoroughly parsed. Apparently it's fast enough for 100 random requests per second. It's 100 times enough :)
                                    4. there is no 4.
                                    This 'works' and fits a particular situation for a particular embedded application: no memory stress, no alien file creation. Currently it's only limited by a minimal support of character types (it's only handling ANSI+unicode).
                                    But definitely it looks like an achievable way to go if you feel somewhat embarassed with existing implementation.
                                    Below is the code source of the main state machine function: contact me for more implementation details.
                                    public TokenType Next(TokenType notifs)
                                    {
                                    _insideElement:
                                        if (_currentType < TokenType.END_ELEMENT)
                                        {
                                            for (; ; )
                                            {
                                                //attribute
                                                switch (_ioBuffer[_bufferIndex])
                                                {
                                                    case ' ':
                                                    case '\t':
                                                    case '\r':
                                                    case '\n':
                                                        _bufferIndex++;
                                                        continue;
                                                    case '>': //end of start element
                                                        _bufferIndex++;
                                                        _depth++;
                                                        goto _beyondElement;
                                                    case '/': //empty element
                                                        _bufferIndex++;
                                                        if (_bufferIndex + 1 > _dataLen)
                                                            PrefetchIO(1);
                                                        if (_ioBuffer[_bufferIndex] == '>')
                                                        {
                                                            _bufferIndex++;
                                                            _tokenEndOffset = _bytesParsed + _bufferIndex;
                                                            _currentType = TokenType.END_ELEMENT; //EMPTY ELEMENT ! it starts and ends on the same token
                                                            if ((notifs & TokenType.END_ELEMENT) != 0)
                                                                return _currentType;
                                                            goto _beyondElement;
                                                        }
                                                        return DoExpected(">");
                                                    case '\0':
                                                        if (!FillIOBuffer())
                                                            return TokenType.END_OF_STREAM;
                                                        continue;
                                                    default:
                                                        _currentType = ProcessAttribute((notifs & TokenType.ATTRIBUTE) != 0);
                                                        if ((notifs & TokenType.ATTRIBUTE) != 0)
                                                            return _currentType;
                                                        continue;
                                    
                                                }
                                            }
                                        }
                                    //
                                    //beyond element: data, or comment, or pi, cdata
                                    _beyondElement:
                                        for (; ; )
                                        {
                                        _start:
                                            switch (_ioBuffer[_bufferIndex])
                                            {
                                                case ' ':
                                                case '\t':
                                                case '\r':
                                                case '\n':
                                                    ++_bufferIndex;
                                                    continue;
                                                case '\0':
                                                    if (!FillIOBuffer())
                                                        return (_currentType = DoEndOfStream());
                                                    continue;
                                                case '<':
                                                    _tokenStartOffset = _bytesParsed + _bufferIndex;
                                                    ++_bufferIndex;
                                                    for (; ; )
                                                    {
                                                        switch (_ioBuffer[_bufferIndex])
                                                        {
                                                            case '!': //comment, cdata, doctype
                                                                _bufferIndex++;
                                                                if (_bufferIndex + 7 > _dataLen)
                                                                    PrefetchIO(7);
                                                                if ((_ioBuffer[_bufferIndex] == '-') && (_ioBuffer[_bufferIndex + 1] == '-'))
                                                                {
                                                                    _bufferIndex += 2;
                                                                    ProcessComment();
                                                                    goto _start;
                                                                }
                                                                if ((_ioBuffer[_bufferIndex] == '[') && (_ioBuffer[_bufferIndex + 1] == 'C') && (_ioBuffer[_bufferIndex + 2] == 'D') &&
                                                                    (_ioBuffer[_bufferIndex + 3] == 'A') && (_ioBuffer[_bufferIndex + 4] == 'T') && (_ioBuffer[_bufferIndex + 5] == 'A') &&
                                                                    (_ioBuffer[_bufferIndex + 6] == '['))
                                                                {
                                                                    _bufferIndex += 7;
                                                                    ProcessCData();
                                                                    goto _start;
                                                                }
                                                                if ((_ioBuffer[_bufferIndex] == 'D') && (_ioBuffer[_bufferIndex + 1] == 'O') && (_ioBuffer[_bufferIndex + 2] == 'C') &&
                                                                    (_ioBuffer[_bufferIndex + 3] == 'T') && (_ioBuffer[_bufferIndex + 4] == 'Y') && (_ioBuffer[_bufferIndex + 5] == 'P') &&
                                                                    (_ioBuffer[_bufferIndex + 6] == 'E'))
                                                                {
                                                                    _bufferIndex += 7;
                                                                    ProcessDocType();
                                                                    goto _start;
                                                                }
                                                                return DoUnexpected("-");
                                                            case '?': //pi
                                                                _bufferIndex++;
                                                                ProcessPI();
                                                                goto _start;
                                                            case '\0':
                                                                //--- fill data
                                                                if (!FillIOBuffer())
                                                                    return (_currentType = TokenType.END_OF_STREAM);
                                                                continue;
                                                            case '/': //end element 
                                                                _bufferIndex++;
                                                                _currentType = ProcessEndElement((notifs & TokenType.END_ELEMENT) != 0);
                                                                if ((notifs & TokenType.END_ELEMENT) != 0)
                                                                    return _currentType;
                                                                goto _start;
                                                            default:  //new element
                                                                //(notifs & TokenType.START_ELEMENT) != 0);
                                                                _currentType = ProcessStartElement(true);//always in full as element name may is extremely usefull to get
                                                                if ((notifs & TokenType.START_ELEMENT) != 0)
                                                                    return _currentType;
                                                                goto _insideElement;
                                                        }
                                    
                                                    }
                                                default: //element data. (probably.)
                                                    _currentType = ProcessData((notifs & TokenType.DATA) != 0);
                                                    if ((notifs & TokenType.DATA) != 0)
                                                        return _currentType;
                                                    goto _start;
                                            }
                                        }
                                    }
                                    

                                    11 May 2010

                                    GFE: Game FrontEnd (emu frontend)

                                    Hey,

                                    I spent the last months fighting/juggling/coding with WPF. Not the easiest hobby in town, should i mention, anyway: this is GFE.

                                    The idea behind the project is to be able to... Play. No fuss, and more generally minimal settings, to be able to list and launch a vast collection of games: emulated, freeware, flash, choice is vast.

                                    Basically GFE is listing a directory of... things (roms, zip, pics, you tell), probably thousands of them.

                                    Background scanners are trying to match every entry with snapshots and movies (furtherly stats and extra infos from web). And apart from primary enumeration, everything else is asynchronous for a smoother experience.

                                    All settings are guessed from a directory structure passed as application argument or preferably from a gameinfo.xml file.

                                    It's using low level inputs, preferably a joystick, enabling some interaction with GFE running in the background. And it's a WPF project: so all the frontend rendering is done through DirectX and accelerated hardware if available.

                                    OK now, this is the very first version of GFE, and, well: it's usable, but probably not versatile or cute enough. I'm working on that: you can help with suggestions, money, or simple greetings.

                                    Binary:  GFE-0.5.0.zip (45 Ko)
                                    Technical details:
                                      Windows - .NET 3.5, works on XP and Seven
                                      Joystick (XInput compatible) or Keyboard
                                    How to run it:
                                      Modify included GameInfo.xml and use it as the application parameter.
                                    Note: when in background, both LB+RB button (Ctrl+Back) is killing launched app and bring back GFE to front. LB+Y exits GFE.

                                    alimbourg at gmail.com for any question.

                                    This is GFE Emu FrontEnd (yes it's all animated, and video is playing from the selected game):