Matching pairs in two observable sequences

For one of my projects that makes heavy use of Reactive Extensions, I needed to pair equal values in two observable sequences.

An example:

var sequence1 = new[]{3, 1, 4, 2}.ToObservable();
var sequence2 = new[]{1, 2, 3, 4}.ToObservable();

var result = sequence1.MatchPair(sequence2).Subscribe(x => Console.WriteLine("{0} {1}", x.Left, x.Right));

This should print

1 1
3 3
2 2
4 4

The distinction between the left and right element must be made, as I want this to work on any class and base the equality on a key selector.

My end result looked was this (you can also find this code here):

public static IObservable<Pair<T>> MatchPair<T, TKey>(this IObservable<T> left, IObservable<T> right, Func<T, TKey> keySelector)
{
    return Observable.Create<Pair<T>>(o =>
    {
        var leftCache = new List<T>();
        var rightCache = new List<T>();
        var gate = new object();
        var disposable = new CompositeDisposable();
        bool leftDone = false;
        bool rightDone = false;

        left.Subscribe(l =>
        {
            lock (gate)
            {
                // Look for the last element, as we want FIFO
                int lastIndex = rightCache.FindLastIndex(x => EqualityComparer<TKey>.Default.Equals(keySelector(x), keySelector(l)));

                if (lastIndex > -1)
                {
                    T element = rightCache[lastIndex];
                    rightCache.RemoveAt(lastIndex);

                    o.OnNext(new Pair<T>(l, element));
                }

                else
                {
                    leftCache.Add(l);
                }
            }
        }, ex =>
        {
            o.OnError(ex);
            disposable.Dispose();
        }, () =>
        {
            lock (gate)
            {
                leftDone = true;

                if (rightDone)
                {
                    o.OnCompleted();
                }
            }
        }).DisposeWith(disposable);

        right.Subscribe(r =>
        {
            lock (gate)
            {
                // Look for the last element, as we want FIFO
                int lastIndex = leftCache.FindLastIndex(x => EqualityComparer<TKey>.Default.Equals(keySelector(x), keySelector(r)));

                if (lastIndex > -1)
                {
                    T element = leftCache[lastIndex];
                    leftCache.RemoveAt(lastIndex);

                    o.OnNext(new Pair<T>(element, r));
                }

                else
                {
                    rightCache.Add(r);
                }
            }
        }, ex =>
        {
            o.OnError(ex);
            disposable.Dispose();
        }, () =>
        {
            lock (gate)
            {
                rightDone = true;

                if (leftDone)
                {
                    o.OnCompleted();
                }
            }
        }).DisposeWith(disposable);

        return disposable;
    });
}

In addition we need the Pair structure that stores the left and right value

public struct Pair<T>
{
    public Pair(T left, T right)
        : this()
    {
        this.Left = left;
        this.Right = right;
    }

    public T Left { get; private set; }

    public T Right { get; private set; }
}

and a small helper method for the CompositeDisposable class (Alternatively you can wrap the subscribe methods in a disposable.Add)

public static T DisposeWith<T>(this T disposable, CompositeDisposable with) where T : class, IDisposable
{
    if(disposable == null)
        throw new ArgumentNullException("disposable");

    if(with == null)
        throw new ArgumentNullException("with");

    with.Add(disposable);

    return disposable;
}

Azure rejects my credit card

Recently, I wanted to give Microsoft Azure a try. Since I'm already registered with Amazon AWS and Google Cloud, a registration with Azure should work too, right?

So I registered with my email address and password, entered my credit card information and pressed the register button.

We can't authorize the payment method. Please make sure the information is correct, or use another payment method. If you continue to get this message, please contact your financial institution.

Wat.

I'm 99.99% sure that I've entered everything correctly and tried it again. Nope, still doesn't work.

After a few days of back and forth with the Azure support, they told me the problem is that my credit card is a Prepaid card. Since I'm from Austria and really only need my credit card for buying small things on the internet, I don't have a different card.

The support also told me:

...the reason why we do not accept the prepaid card is because in past we have seen the subscriptions being disabled due to insufficient funds in the prepaid card.

While I can understand the reasoning behind this, I wonder why AWS and Google Cloud accept my credit card.

Well, seems like no Azure for me.

Link Github issues with Javascript

Here is a quick code snippet that I used in the new Mahapps.Metro News page to link Github issue numbers to their corresponding URL:

$(function() {
  $(".yourclass").each(function(element) {
    var replaced = $(this).html().replace(/(#([0-9]+))/g, '<a href="https://github.com/<your-repo-here>/issues/$2">$1</a>');
    $(this).html(replaced);
  });
})

Since we're keeping our release notes in our repository and just copy paste it into the Github releases section, this is a neat way to avoid linking each issue manually.

Better open source collaboration with Gitter

With Mahapps.Metro we've always had the problem that discussing new plans with the core team and the contributors, or questions from the users didn't really work over Github issues. The core team communicated over Skype and we also had an open IRC channel, which was really cumbersome since IRC doesn't preserve the chat history without everyone installing a bot that logs the history on their own server.

Today I discovered Gitter, a fancy new service that allows people to create chatrooms for projects on Github.

They're currently invite only (meaning only people with invites can create chatrooms but people without invites can join them), but after asking I almost immediately got an invite and created the MahApps.Metro chatroom

Gitter has an awesome Github integration, there's a configurable activity log on the right side of the chat where recent issues, pull requests, commits, etc are displayed. The chat also supports auto-linking of issues, code snippets and Markdown support.

After only a few hours, I've got most of our current contributors to switch from IRC to Gitter and what should I say, it's been an awesome experience.

The team of Gitter responds insanely fast to questions on Twitter and you can also drop in on the GitterHQ chatroom and ask them questions directly (they even changed that you don't need to be the owner of an organization, but only require push access in order to create a chatroom for a organization repository after I asked them if it was possible)

The bad parts

I don't have much to write here.

Gitter requires write access to your Github account, this is a limitation of the Github API, they have it described here and here

This isn't too much of a problem for me.

Update: Github has since updated their access permission model and Gitter doesn't require write access anymore

Conclusion

Gitter is an awesome piece of work and I believe it will simplify the collaboration on the projects I'm working on.

The only thing that's currently missing for me is an Android app, but from their website it seems that this is in work.

Ruby's Net::Http doesn't like redirects and https

I'm currently experimenting with ruby and one thing I needed to do, was retrieving the content of an external website.

My first approach to this was using the Net::Http.get method. The problem: this doesn't follow redirects.

My second approach was a more complicated version that catches a redirect and requests the content again with the redirect url.

Taken from the ruby docs

require 'net/http'
require 'uri'

def fetch(uri_str, limit = 10)
  # You should choose a better exception.
  raise ArgumentError, 'too many HTTP redirects' if limit == 0

  response = Net::HTTP.get_response(URI(uri_str))

  case response
  when Net::HTTPSuccess then
    response
  when Net::HTTPRedirection then
    location = response['location']
    warn "redirected to #{location}"
    fetch(location, limit - 1)
  else
    response.value
  end
end

The problem: If the website redirects to https, it will fail with a 400 HttpBadRequest.

Solution

After a bit of research, I stumbled over the httpclient gem.

require 'httpclient'

client = HTTPClient.new
content = client.get_content("http://example.com")

Well, this looks much better!