Friday, March 06, 2009

NetCenter CRM

For the last month or so, I've been working on "NetCenter" a Grails 1.1 based CRM system that will integrate with sipX for call detail records, Zimbra or Exchange 2007 for email, calendaring, and time tracking purposes, and finally Alfresco or Sharepoint for document management.

I've really enjoyed using Grails - its a real productivity booster and I really appreciate the Separation of concerns you get with an MVC framework.

I completed the sipX integration first and am now working with Exchange 2007 Web Services so that users can associate meetings with accounts and mark them billable/non-billable.

First a few screenshots, then a brief overview of the sipx integration. Note: in the screenshots below the account and contact information is randomly generated test data, while the call records are real records coming out of our production sipX server.

Call Manager:


Account Calls:


Contact Calls:


I used the Grails Quartz Plugin and added a grails-app/jobs/CdrSyncJob.groovy that looks at licensees with registered sipX servers and then queries with sipX instance for call detail records that have not yet been processed.

I wanted call detail report generation to be as fast as possible, so the CdrSyncJob looks up the sipX callee and caller phone numbers against the contact table and licencedUser table then writes a new "call" record into the NetCenter database and marks the sipX call record has having been processed so it can be ignored the next time the job runs. Now whenever anyone wants to view all calls made to any contact within a certain account, its a simple database query that has a few joins and doesn't involve any phone number normalization, determining whether a call is related to any known contact, ignoring interoffice calls, or figuring out the call direction.

Here a few snippets for CdrSyncJob. First the execute() method:
def execute() {

if (Environment.current == Environment.DEVELOPMENT) return
def licensees = Licensee.withCriteria {
eq("active", true)
isNotNull("sipHost")
}

licensees.each { syncCdrs(it); }
}
Then syncCdrs begins with some Groovy SQL like this:
   def cdr = Sql.newInstance("jdbc:postgresql://${licensee.sipHost}/SIPXCDR", "username", "password", "org.postgresql.Driver")
cdr.eachRow("select * from view_call_records A, cdrs_sync B where A.id=B.id and NOT(B.done)")
Hmmm, I guess I should point out that view_call_records and cdrs_sync are custom tables. Here's the SQL:
CREATE VIEW view_call_records as
select id, SUBSTRING(caller_aor FROM '.*.*') as caller,
LTRIM(LTRIM(SUBSTRING(callee_aor FROM '.*.*'), '8'), '1') as callee,
connect_time as start_time,
to_char(cdrs.end_time-cdrs.connect_time, 'MI') AS minutes,
to_char(cdrs.end_time-cdrs.connect_time, 'SS') as seconds
from cdrs where cdrs.termination != 'F' and cdrs.connect_time IS NOT NULL;

CREATE TABLE cdrs_sync (
id integer PRIMARY KEY,
done boolean DEFAULT FALSE
);
Anyway, the rest of syncCdrs is just about ignoring interoffice calls or calls to contacts with don't have on record, then adding new entries to the NetCenter call table:
new Call(callDirection: direction, callId: it.id, contact: contact, dateStarted: it.start_time, minutes: it.minutes, seconds: it.seconds, licensee: licensee, owner: owner).save();
and marking the call as processed in the cdrs_sync table.

Next time I get a chance to blog, I hope to show the Exchange integration and some jQuery snippets. jQuery has been a big productivity booster as well. Web development has come along way!

Tuesday, January 13, 2009

Integrating sipX with ejabberd

I recently completed integrating our sipX based voip platform with our ejabberd XMPP server, so that users can see when others are on the phone or not. There are alot of similar integrations that people have done with Asterisk using their AMI api, but I haven't found anything similar for sipX yet, so we rolled our own for now. While, it's not terribly exciting, here's a screenshot of what it looks like when someone is on the phone:


The solution I came up with involves 3 parts. First, I setup a clustered RabbitMQ server (an open source implementation of AMQP). I plan on using it to facilitate a loosely coupled, event driven architecture for integrating multiple open source
applications. I'm pretty happy with RabbitMQ thus far - about the only complaint I have is that they don't have any message tracing capabilities right now (version 1.5.0) which made it more difficult to debug my client side code. I'm also hoping that sometime soon we start seeing debian packages for python/perl amqp libraries. For now, I'm using Net::Stomp and the RabbitMQ stomp adapter which seemed like the most stable, easily deployed client side solution.

On the XMPP server side, I created an erlang module that acts as a message consumer. Each virtual host in our ejabberd server listens on a separate queue for presence messages generated by the sipX side and sends out XMPP presence updates to online sessions.

After getting the RabbitMQ erlang client library installed, here's the code I used to connect and setup my consumer:
Connection = amqp_connection:start(Uname, Pwd, "mq.nvizn.com"),
Channel = amqp_connection:open_channel(Connection),
Qname = list_to_binary("/" ++ Host ++ "/presence/phone"),
Q = lib_amqp:declare_queue(Channel, Qname),
lib_amqp:bind_queue(Channel, <<"">>, Q, Qname),
lib_amqp:subscribe(Channel, Q, self(), false),

Then I created a handle_info function that looks like this:

handle_info({ {'basic.deliver', DeliveryTag, _, _, _, _ },
{content, ClassId, Properties, PropertiesBin,
[Payload]} = Info}, State) ->

%% Message processing here, then send out the XMPP presence update...,
BroadcastPresence = fun({U, S, R}) ->
Dest = jlib:make_jid(U, S, R),
ejabberd_router:route(FromJID, Dest, Presence)
end,
Sessions = ejabberd_sm:get_vh_session_list(State#state.host),
lists:foreach(BroadcastPresence, Sessions),
Now on the sipX side, things are a bit more ugly, and when I have more time later, I'd like to rework this end. For now, I created a PL/pgSQL AFTER trigger on SIPXCDR.call_state_events table that handles new call state events ('S' and 'E' event_types to be specific). This trigger inserts new rows into a new cse_summary table I created for every call, one for when the call is setup and one for call termination and it does this for each internal user. If the call involves two internal folks, you end up with 4 rows, if on the other hand, one side is external, you end up with only 2 rows. This trigger also looks up the XMPP jid for the extension and records that in the generated cse_summary rows.

When a row is created in the cse_summary table, a separate
PL/Perl AFTER trigger uses Net::Stomp to generate a call state
event message for the RabbitMQ cluster.

Here's what the PL/Perl trigger looks like:
my $stomp = Net::Stomp->new({hostname=>'mq.nvizn.com',port=>'61613'});
$stomp->connect({login=>$uid, passcode=>$pwd});

my $msg = sprintf("%s,%s,%s", $domain,
$_TD->{"new"}{"event_type"}, $_TD->{"new"}{"jid"});

$stomp->send({destination=>"/$domain/presence/phone", body=>($msg)});
$stomp->disconnect;
Now, I'm just creating some debian packages and RPMs (for the sipX side), documenting how it works, and thinking about our next integration.

Saturday, December 06, 2008

Load Balance Clustered Ejabberd Servers

I recently completed setting up our XMPP infrastructure. After spending some time reviewing the current capabilities of jabberd2, openfire, djabberd, and ejabberd, I decided that ejabberd had the best combination of features for our needs: virtual hosting, LDAP integration, clustering support, shared rosters, and reasonably good documentation!

So after setting up the first ejabberd node (im1), with a test virtual host and working LDAP integration, I setup our second ejabberd node (im2) by copying /etc/ejabberd/ejabberd.cfg to the 2nd node, then running through the following steps:

  • First launch an erlang shell as the ejabberd user, with erl -sname ejabberd@im2 -mnesia extra_db_nodes "['ejabberd@im1']" -s mnesia

  • Then, to replicate all ejabberd tables in my configuration, I ran a: mnesia:change_table_copy_type(schema, node(), disc_copies).mnesia:add_table_copy(offline_msg,node(),disc_only_copies). mnesia:add_table_copy(privacy,node(),disc_copies). mnesia:add_table_copy(sr_group,node(),disc_copies). mnesia:add_table_copy(sr_user,node(),disc_copies). mnesia:add_table_copy(roster,node(),disc_copies). mnesia:add_table_copy(last_activity,node(),disc_copies). mnesia:add_table_copy(disco_publish,node(),disc_only_copies). mnesia:add_table_copy(pubsub_node,node(),disc_copies). mnesia:add_table_copy(pubsub_state,node(),disc_copies). mnesia:add_table_copy(pubsub_item,node(),disc_only_copies). mnesia:add_table_copy(session,node(),ram_copies). mnesia:add_table_copy(s2s,node(),ram_copies). mnesia:add_table_copy(route,node(),ram_copies). mnesia:add_table_copy(iq_response,node(),ram_copies). mnesia:add_table_copy(caps_features,node(),ram_copies). mnesia:add_table_copy(motd_users,node(),disc_copies). mnesia:add_table_copy(motd,node(),disc_copies). mnesia:add_table_copy(acl,node(),disc_copies). mnesia:add_table_copy(config,node(),disc_copies).

    After you quit the shell, you'll most likely need to move the result mnesia database files to the ejabberd user's $HOME folder.

    Once, both nodes were working correctly I setup a LVS-DR load balancer with ldirectord. This proves to be rather straightforward.

    First the realservers (each ejabberd instance, im1 and im2) had to configured with a local interface that listens to the load balancer's VIP (virtual IP). The most reliable way I found to set this up was with a simple
    ip addr add 172.16.254.60/32 brd + dev lo label lo:vip
    in /etc/rc.local.

    Then I setup a /etc/sysctl.d/60-ipvs-arp-rules.conf with
    net.ipv4.conf.eth0.arp_ignore = 1
    net.ipv4.conf.eth0.arp_announce = 2
    net.ipv4.conf.all.arp_ignore = 1
    net.ipv4.conf.all.arp_announce = 2
    On Ubuntu (and I think debian as well), you must also tweak /etc/sysctl.d/10-network-security.conf to disable source address validation
    net.ipv4.conf.default.rp_filter=0
    net.ipv4.conf.all.rp_filter=0
    That's pretty much it for the realservers.

    Setting up the loadbalancer involves setting up the VIP in /etc/network/interfaces
    auto eth0:vip0
    iface eth0:vip0 inet static
    address 172.16.254.60
    broadcast 172.16.254.60
    netmask 255.255.255.255
    Then setting up ldirectord (apt-get install ldirectord) in /etc/ldirectord.cf with
    /etc/ldirectord.cf
    # Global Directives
    checktimeout=3
    checkinterval=15
    autoreload=yes
    logfile="/var/log/ldirectord.log"
    logfile="local0"
    emailalert="joel.reed@nvizn.com"
    emailalertfreq=3600
    emailalertstatus=all
    quiescent=yes

    virtual=172.16.254.60:5222
    real=172.16.254.70:5222 gate
    real=172.16.254.72:5222 gate
    scheduler=wlc
    protocol=tcp
    checktype=negotiate
    service=simpletcp
    request="junk"
    receive="jabber.org"
    It'd be really cool if there was some kind of builtin heathcheck call you could do on an ejabberd node, but alas there isn't so I just send it a string of garbage ("junk" to be exact), and look for the jabber.org string in the XMPP response. Seems to be working OK thus far...
  • Monday, November 03, 2008

    Alfresco on EC2

    Over the weekend, I created a Alfresco Labs 3b AMI on EC2, Amazon's cloud computing platform.

    I took one of the Alestic Ubuntu 8.10 base images, added my own ec2-tools_0.1.deb package, and built out an AMI with Labs 3b running on the system tomcat5.5, instead of the bundled tomcat instance. That part was far more brutal than using EC2. You have to make quiet a few changes to the catalina policy to get things working.

    I made an Alfresco package, that installs an /etc/tomcat5.5/policy.d/60alfresco.policy file that looks like this:
    grant { 
    permission java.lang.RuntimePermission "accessClassInPackage.org.apache.*";

    permission java.lang.RuntimePermission "accessDeclaredMembers";
    permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
    permission java.util.PropertyPermission "alfresco.jmx.dir", "read,write";
    permission java.util.PropertyPermission "webapp.root", "read,write";
    permission java.io.FilePermission "/usr/share/java/servlet-api-2.4.jar", "read";
    };

    grant codeBase "file:${catalina.home}/bin/tomcat-juli.jar" {
    permission java.io.FilePermission "/usr/share/tomcat5.5/webapps/alfresco/WEB-INF/classes/logging.properties", "read";
    permission java.io.FilePermission "/var/lib/tomcat5.5/temp/-", "read,write,delete,execute";
    permission java.io.FilePermission "/var/lib/tomcat5.5/temp", "read,write,execute";
    }
    All of my AMIs have a rebundle.sh script that can quickly upload an updated AMI. It looks something like this:
    #!/bin/sh
    ACCOUNTID=xxxxxx
    CERTFILE=/etc/ec2/xxxxxxx.pem
    KEYFILE=/etc/ec2/xxxxxxx.pem
    ACCESSKEY=xxxxxxxxxxx
    SECRETKEY=xxxxxxxxxx

    umount /var/local
    ec2-bundle-vol -u $ACCOUNTID -c $CERTFILE -k $KEYFILE -p ubuntu-8.10-appsuite-1.0-20081101 --ec2cert /etc/ec2/amitools/cert-ec2.pem -r i386
    ec2-upload-bundle -b nvizn.com -m /tmp/ubuntu-8.10-appsuite-1.0-20081101.manifest.xml -a $ACCESSKEY -s $SECRETKEY
    ec2-register nvizn.com/ubuntu-8.10-appsuite-1.0-20081101.manifest.xml
    This made life a bit easier as I made changes to the image and uploaded them. I unmount /var/local at the start of the script as that's where I mount my EBS volume.

    Monday, October 20, 2008

    Samba4 on Ubuntu Intrepid

    Here's a brief rundown of my experiences with Samba4 on Ubuntu Intrepid.

    I first tried the samba4 package in the ubuntu intrepid repositories, but when you do a
    ./setup/provision --realm=azulogic.com --domain=azulogic --adminpass=fubar --server-role='domain controller'
    you get a python stackdump with
    IOError: [Errno 2] No such file or directory: '/usr/etc/samba/smb.conf'
    I tried creating a "/usr/etc/samba" folder (though the distaste was high), but then proceeded to get further file path errors.

    So, next I switched to the Debian Experimental package. This worked much better.

    After you apt-get install the package, you'll have to fixup /etc/init.d/samba4 - it's still looking for smbd (the samba3 daemon), whereas in samba4 its now /usr/sbin/samba.

    So, I just did a
    ln -s /usr/sbin/samba /usr/sbin/smbd
    to get it to work.

    After getting krb5, dns, and samba ready to go, I tried to join a linux machine running winbind 2:3.2.3-1ubuntu3 to the domain. No luck though:
    (~) net ads join -U Administrator
    Enter Administrator's password:
    Failed to join domain: failed to lookup DC info for domain 'AZULOGIC.COM' over rpc: NT_STATUS_INTERNAL_ERROR
    How do you fix this? One way is to run in the "single" process model mode. I changed /etc/init.d/samba4 to launch the samba daemon with -M single. Then you see a nice:
    (~) net ads join -U Administrator
    Enter Administrator's password:
    Using short domain name -- AZULOGIC
    Joined 'LTS' to realm 'azulogic.com
    One final note: as far as I can tell the debian version (4.0.0alpha6-GIT-7fb9007) crashes when someone tries to do a change password. So beware!

    Thursday, October 16, 2008

    Secure Apt Repository Howto

    After a good bit of googling and poking around, I completed the setup of our secure apt repository here at nvizn.

    Here's how you'd do it for an Ubuntu intrepid repository.

    First, setup a directory tree that looks like this:
    mkdir -p /var/www/packages/dists/intrepid/main/binary-i386/
    mkdir -p /var/www/packages/intrepid/main
    Then, install apt-ftparchive, which will do most of the heavy lifting.
    apt-get install apt-ftparchive
    Now, drop all your .debs into /var/www/packages/intrepid/main/ and create an apt-ftparchive configuration file at /etc/archive.config

    Here's what mine looks like:
    Dir {
    ArchiveDir "/var/www/packages";
    CacheDir "/home/joel.reed/uploads/";
    };

    Default {
    Packages::Compress ". gzip bzip2";
    Sources::Compress ". gzip bzip2";
    Contents::Compress ". gzip bzip2";
    };

    APT::FTPArchive::Release::Codename "intrepid";
    APT::FTPArchive::Release::Suite "intrepid";
    APT::FTPArchive::Release::Origin "Joel W. Reed";

    TreeDefault {
    BinCacheDB "packages-$(SECTION)-$(ARCH).db";
    Directory "intrepid/$(SECTION)";
    Packages "$(DIST)/$(SECTION)/binary-$(ARCH)/Packages";
    SrcDirectory "intrepid/$(SECTION)";
    Sources "$(DIST)/$(SECTION)/source/Sources";
    Contents "$(DIST)/Contents-$(ARCH)";
    };

    Tree "dists/intrepid" {
    Sections "main";
    Architectures "i386";
    }
    Finally, run this sequence of commands:
    apt-ftparchive generate /etc/archive.config
    cd /var/www/packages/dists/intrepid/
    apt-ftparchive -c /etc/archive.config release . > Release
    rm -v Release.gpg
    gpg -v --output Release.gpg -ba Release
    When you're done, you'll end up with a /var/www/packages tree that looks something like this:
    /var/www/packages/dists/intrepid
    /var/www/packages/dists/intrepid/main
    /var/www/packages/dists/intrepid/main/binary-i386
    /var/www/packages/dists/intrepid/main/binary-i386/Packages.gz
    /var/www/packages/dists/intrepid/main/binary-i386/Packages.bz2
    /var/www/packages/dists/intrepid/main/binary-i386/Packages
    /var/www/packages/dists/intrepid/Contents-i386
    /var/www/packages/dists/intrepid/Release
    /var/www/packages/dists/intrepid/Release.gpg
    /var/www/packages/dists/intrepid/Contents-i386.gz
    /var/www/packages/dists/intrepid/Contents-i386.bz2
    /var/www/packages/intrepid
    /var/www/packages/intrepid/main
    /var/www/packages/intrepid/main/alfresco-r3184-0.3.1.deb
    /var/www/packages/intrepid/main/nvizn-base-0.3.6.deb
    /var/www/packages/intrepid/main/libnss-cache_0.1-1_i386.deb
    /var/www/packages/intrepid/main/nsscache_0.8.4.1_all.deb
    /var/www/packages/intrepid/main/stratus-desktop-0.2.deb
    /var/www/packages/intrepid/main/packages-main-i386.db
    /var/www/packages/intrepid/main/jsetup_0.5.1_all.deb
    Now, to make all this work, you need to have a gpg key of course, and apache set to serve up /var/www/packages, and all client machines need the public key. To do that with a key on a keyserver, do something like
    gpg --recv-keys B1850655 && gpg --export B1850655 | apt-key add -
    Hope this is helpful to you!

    Monday, October 13, 2008

    Startup

    I haven't blogged for while, because I've been putting a lot of hours into an open source startup company. It's been great fun to work with some new technologies like Groovy, Grails, CouchDB, and Samba4.

    Among other things, I setup an openldap server, built a few custom www.openldap.org/lists/openldap-software/200807/msg00002.html">overlays, and integrated Zimbra, Alfresco, Openfire, SipX, Samba3, and an Ubuntu desktop. Each of these integrations has there pros and cons, perhaps Zimbra and SipX are the nicest.

    I'm hoping to blog about my experience with Samba4 shortly.

    Monday, February 04, 2008

    OpenTF 0.6.0 Release

    Wow - two months without a blog post and 3 months since my last OpenTF release! For the last month or two, I really haven't worked much on OpenTF, preferring instead to work on learning NT Greek and more about the Book of Isaiah.

    The latest release includes a few new goodies and many bugfixes. There's the new IRC changeset notification bot, support for CruiseControl (an open source continuous build framework), a monodevelop plugin for browsing TFS servers, and several new commands like "shelve", "rollback", and "merges".

    Over the next few month, I hope to be able to further develop the monodevelop plugin, continue work on missing commands, and begin testing other open source Team Foundation tools for compatibility with the OpenTF libraries.

    Friday, November 30, 2007

    Job Openings

    If there's anyone looking for a ASP.Net developer position in the Pittsburgh (PA) area and you've contributed to the mono project in the past, please put a link to your resume in the comments for this post. We're a great company to work and are early adopters of .Net related technologies. I'd love to be able to hire folks who have helped out the mono project. Thanks!

    Thursday, November 08, 2007

    MonoDevelop and Team Foundation

    Now that MonoDevelop is nearing a 1.0 release, I thought I'd take another look at fleshing out a TeamFoundation plugin for MD.

    For starters, I'm taking the "tf explore" command in OpenTF, factoring out the Gtk classes into a separate assembly, then building out an MD addin that makes use of it.

    Obligatory screenshot: OpenTF-MonoDevelop-v1

    It will take a while to clean up this code and the build machinery, and to figure out how to make better use of builtin MonoDevelop addin services, but perhaps in a release of two, we'll have something useful. If you're interested in helping, please do!

    At some point, I'm also hopeful that those developing the VersionControl API for MD can consider the needs of a Team Foundation plugin. I'd be very interested in seeing if we can make something work for SVN, GIT, etc. and TFS. That'd be a much better situation.

    Monday, October 29, 2007

    OpenTF Build Changes

    I started the OpenTF project out by copying the Mono Olive tree, and replacing its assemblies and tools with my Team Foundation files. This worked well on *nix, but recently I've been trying to improve support for building on Windows as well.

    Should I use cscript, nmake, powershell, BAT, project files, or some combination of these? How could I implement a build solution that didn't just duplicate the same build instructions (source files, references, etc) in 2 different formats: one for windows and one for *nix?

    I decided to keep things simple on Windows - just use a VS2005 solution with a bunch of project files. Then for *nix, I decided to make libxslt's xsltproc a build requirement, and generate the list of sources and references for the mono olive make machinery using a few simple XSL stylesheets.

    For example, all the .sources files are now generated via build/sources.xsl. Which looks something like this:
      <xsl:template match="/">
    <xsl:apply-templates select="Project/ItemGroup/Compile"/>
    </xsl:template>

    <xsl:template match="Compile">
    <xsl:value-of select="@Include" /><xsl:text> </xsl:text>
    </xsl:template>
    I also have .references files for each assembly, also generated via an XSL file from the .csproj.

    Now, I just maintain the VS2005 project files, and leave the *nix build stuff to the stylesheets. I added support for conditional sources using the Conditional attribute. Its working quite well thus far.

    Tuesday, October 02, 2007

    Using git-svn with Mono

    Why use git to hack on mono?

    I've found myself far more productive and make heavy use of feature branches and squashed commits for my day job, so when I hack on mono, I really enjoy being able to leverage the same capabilities.

    By "squashed commits", I guess I should really say, leveraging the power of a distributed version control system that lets me break down a task into many smaller steps, commit each step individually, then squashing the whole thing down to one patch that I can post to mono-devel for review.

    So do you set things up to use git with Mono?
    cd /usr/local/src/
    mkdir mono && cd mono
    mkdir mcs && cd mcs
    git-svn init svn+ssh://username@mono-cvs.ximian.com/source/trunk/mcs
    git-svn fetch -r 86200 && git-svn fetch
    cd ..
    mkdir mono && cd mono
    git-svn init svn+ssh://username@mono-cvs.ximian.com/source/trunk/mono
    git-svn fetch -r 86200 && git-svn fetch
    Note: the above recipe copies the svn history only back to revision 86200. You can pick any valid svn revision number you like, or if you want the full revision history see this page on the Mono wiki.

    Ok. Everything's setup. Now what?

    First, let's say I want to hack on some ASP.NET ashx page bug. I'll setup a local branch "ashx" to store whatever code I write/change:
    git-checkout -b ashx
    Now I have two branches: "master" which was setup by git-svn above, and "ashx" which I just created and switched over to. Now, I can:
    emacs -nw class/System.Web/...
    git-commit -a -m "1st step"
    emacs -nw class/System.Web/...
    git-commit -a -m "2nd step"
    emacs -nw class/System.Web/...
    git-commit -a -m "3rd step"
    Ok, now to post a message to mono-devel:
    git-diff master ashx > ~/Bug6884.fix
    mutt
    When everything looks good and no one has any complaints, I can finally commit back to mono's svn repository with:
    git-branch master
    git-pull --squash --summary . ashx
    git-commit -a -m "message for mono's svn"
    git-svn dcommit
    This "squashes" my commits down into one batch of changes on the master branch, which I then commit and push to svn repo.

    Finally, How do I update my local tree?
    git-svn fetch && git-svn rebase remotes/git-svn
    Note: This will update the current branch you are on locally.

    Hope someone finds this helpful!

    Thursday, September 20, 2007

    test RPM for tf4mono

    I just caught up on my reading of the mono mailing list and saw Miguel's post about Mono Packaged .NET apps for Mono.

    Since I have debian packages and a win32 installer for tf4mono, I thought it might be time to make an RPM package as well and maybe help this QA effort.

    Anyway, I downloaded the very helpful Mono 1.2.5 VMWare image and went to work on creating a spec file for rpmbuild. Side note: cleaning out the bash history and ~/.ssh might be a sensible improvement to this image.

    I had to try and remember all the old rpm command line options I used to use in my sleep - as I fell in love with debian's apt-get several years ago and forgot most rpm incantations.

    Anyway, here's the resultant RPM package. By the way, I enabled the optional gtksourceview-sharp based syntax highlighting in the package.

    If anyone can review the tfs.spec.in file or the RPM file and offer suggestions for improvement, please do so. I'll gladly make a necessary cleanups.

    I installed the package and ran "tf show build" and "tf show stats /server:my.tfs.server.ip" and "tf explore /server:my.tfs.server.ip" and everything seemed in order. The "tf show" commands are new in the soon to be released 0.5.2 version of tf4mono.

    Friday, September 07, 2007

    Monthly Sleep Deprivation

    Oddly enough, I've seem to have fallen into a schedule of releasing updates
    to tf4mono about once a month. This month's release is
    tf4mono 0.5.1
    which includes win32 installation packages, a GTK-based gui mode
    for exploring TFS repositories, many command enhancements, improved builtin help with
    usage guidelines, and numerous bugfixes.

    I always need a bit of downtime after a release first of course -
    working on open source software as a hobby is fun,
    but always ends up meaning lost sleep every so often.

    Anyway, I'm interested in hearing what features would make tf4mono
    more useful to you. Better support for locking files? Handling merge conflicts?
    Easier building on win32 platforms? More GUI support? Let me know!

    The master/trunk branch of tf4mono just got a "stats" command which makes use of
    /VersionControl/v1.0/administration.asmx to generate some server statistics.

    Here's some sample output:
    (~/Source/tfs-lsg-1.0) tf stats
    Files: 812421
    Folders: 20033
    Groups: 481
    Pending Changes: 7907
    Shelvesets: 180
    Users: 184
    Workspaces: 154
    I plan on augmenting this output a bit, but its a good start.

    The most nagging issue for me is actually a NTLM bug in mono that I keep hoping someone will eventually fix. It could be bug
    #80687, though I'm not sure of it. On windows boxen, tf4mono never gives occasional auth failures, but on mono it does - especially on a fast network.

    By accident, I noticed that I never saw any auth failures when working from home
    over a VPN, but at work I'd see the auth failures quite regularly. If I route my
    TFS traffic at work thru my home machine, thru the VPN, and back to work I never see auth failures. So it seems the faster the network the more likely you are to see this NTLM bug in mono.

    Thursday, August 30, 2007

    tf4mono for windows

    Thanks to Nullsoft Scriptable Install System, I've created some win32 installation packages for tf4mono.

    There are two install options.

    The first, tf4mono-base-0.5.1-rc1.exe, has been compiled without any GUI code. It has no external dependencies and should run on any win32 box with the .Net 2.0 framework installed.

    The second package, tf4mono-full-0.5.1-rc1.exe, includes the graphical TF explore command. To run this version on win32, you must first install the Gtk# Installer for Windows.

    Neither package adds the tf4mono installation folder to the SYSTEM or USER path. You'll have to do this by hand for now.

    If you're on a windows box and have a few minutes to test out the package, please do so. Any feedback on how they work would be awesome.