From 44d588baf1f9324ced16e003d0209bd1e3c546ab Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Tue, 27 Mar 2018 18:32:09 +0200
Subject: Fix error response handling when requesting VCards

When fetching a vCard, an empty vCard response and an
item-not-found error are semantically the same. Changed the
code to treat and item-not-found error as an empty vCard in
this case.

This enables setting your own vCard on servers that do not
return an empty vCard for fresh accounts and generally
improves UX when fetching others vCards.

Test-Information:

Added unit tests verifying new behaviour. Tested with Swift
against a Prosody IM instance. Without this change you cannot
set the vCard on a fresh user. With this patch you can set your
own vCard.

Change-Id: I5f9adb4c3d6b6a1a320b834be918ab5ab0b52975

diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md
index a00f41c..3516022 100644
--- a/Swift/ChangeLog.md
+++ b/Swift/ChangeLog.md
@@ -1,3 +1,7 @@
+4.0.1 (2018-03-28)
+------------------
+- Allow setting vCard on servers that do not return an empty vCard on fresh accounts
+
 4.0 (2018-03-20)
 ----------------
 - New chat theme including a new font
diff --git a/Swiften/ChangeLog.md b/Swiften/ChangeLog.md
index a664341..23d5185 100644
--- a/Swiften/ChangeLog.md
+++ b/Swiften/ChangeLog.md
@@ -1,4 +1,8 @@
-4.0 (2017-03-20)
+4.0.1 (2018-03-28)
+------------------
+- Fix handling errors when fetching own vCard
+
+4.0 (2018-03-20)
 ----------------
 - Moved code-base to C++11
     - Use C++11 threading instead of Boost.Thread library
diff --git a/Swiften/VCards/UnitTest/VCardManagerTest.cpp b/Swiften/VCards/UnitTest/VCardManagerTest.cpp
index 3d5338d..669c3ff 100644
--- a/Swiften/VCards/UnitTest/VCardManagerTest.cpp
+++ b/Swiften/VCards/UnitTest/VCardManagerTest.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -31,7 +31,17 @@ class VCardManagerTest : public CppUnit::TestFixture {
         CPPUNIT_TEST(testRequest_Error);
         CPPUNIT_TEST(testRequest_VCardAlreadyRequested);
         CPPUNIT_TEST(testRequest_AfterPreviousRequest);
-        CPPUNIT_TEST(testRequestOwnVCard);
+
+        CPPUNIT_TEST(testRequestVCard_ReturnFullVCard);
+        CPPUNIT_TEST(testRequestVCard_ReturnEmptyVCard);
+        CPPUNIT_TEST(testRequestVCard_ReturnItemNotFoundError);
+        CPPUNIT_TEST(testRequestVCard_ReturnFeatureNotImplementedError);
+
+        CPPUNIT_TEST(testRequestOwnVCard_ReturnFullVCard);
+        CPPUNIT_TEST(testRequestOwnVCard_ReturnEmptyVCard);
+        CPPUNIT_TEST(testRequestOwnVCard_ReturnItemNotFoundError);
+        CPPUNIT_TEST(testRequestOwnVCard_ReturnFeatureNotImplementedError);
+
         CPPUNIT_TEST(testCreateSetVCardRequest);
         CPPUNIT_TEST(testCreateSetVCardRequest_Error);
         CPPUNIT_TEST_SUITE_END();
@@ -54,7 +64,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testGet_NewVCardRequestsVCard() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             VCard::ref result = testling->getVCardAndRequestWhenNeeded(JID("foo@bar.com/baz"));
 
             CPPUNIT_ASSERT(!result);
@@ -63,7 +73,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testGet_ExistingVCard() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             VCard::ref vcard(new VCard());
             vcard->setFullName("Foo Bar");
             vcardStorage->setVCard(JID("foo@bar.com/baz"), vcard);
@@ -75,7 +85,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testRequest_RequestsVCard() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             testling->requestVCard(JID("foo@bar.com/baz"));
 
             CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
@@ -83,7 +93,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testRequest_ReceiveEmitsNotification() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             testling->requestVCard(JID("foo@bar.com/baz"));
             stanzaChannel->onIQReceived(createVCardResult());
 
@@ -96,7 +106,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testRequest_Error() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             testling->requestVCard(JID("foo@bar.com/baz"));
             stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID()));
 
@@ -105,7 +115,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testRequest_VCardAlreadyRequested() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             testling->requestVCard(JID("foo@bar.com/baz"));
             VCard::ref result = testling->getVCardAndRequestWhenNeeded(JID("foo@bar.com/baz"));
 
@@ -114,7 +124,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testRequest_AfterPreviousRequest() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             testling->requestVCard(JID("foo@bar.com/baz"));
             stanzaChannel->onIQReceived(createVCardResult());
             testling->requestVCard(JID("foo@bar.com/baz"));
@@ -123,8 +133,60 @@ class VCardManagerTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(1, JID("foo@bar.com/baz"), IQ::Get));
         }
 
-        void testRequestOwnVCard() {
-            std::shared_ptr<VCardManager> testling = createManager();
+        void testRequestVCard_ReturnFullVCard() {
+            auto testling = createManager();
+            testling->requestVCard(JID("foo@bar.com/baz"));
+            stanzaChannel->onIQReceived(createVCardResult());
+
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+            CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID("foo@bar.com/baz"), IQ::Get));
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+            CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), changes[0].first);
+            CPPUNIT_ASSERT_EQUAL(std::string("Foo Bar"), changes[0].second->getFullName());
+            CPPUNIT_ASSERT_EQUAL(false, changes[0].second->isEmpty());
+        }
+
+        void testRequestVCard_ReturnEmptyVCard() {
+            auto testling = createManager();
+            testling->requestVCard(JID("foo@bar.com/baz"));
+                  stanzaChannel->onIQReceived([&](){
+                    auto vcard = std::make_shared<VCard>();
+                    return IQ::createResult(JID("foo@bar.com/baz"), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID(), vcard);
+            }());
+
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+            CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID("foo@bar.com/baz"), IQ::Get));
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+            CPPUNIT_ASSERT_EQUAL(true, changes[0].second->isEmpty());
+        }
+
+        void testRequestVCard_ReturnItemNotFoundError() {
+            auto testling = createManager();
+            testling->requestVCard(JID("foo@bar.com/baz"));
+                  stanzaChannel->onIQReceived([&](){
+                    return IQ::createError(JID("foo@bar.com/baz"), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID(), ErrorPayload::ItemNotFound);
+            }());
+
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+            CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID("foo@bar.com/baz"), IQ::Get));
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+            CPPUNIT_ASSERT_EQUAL(true, changes[0].second->isEmpty());
+        }
+
+        void testRequestVCard_ReturnFeatureNotImplementedError() {
+            auto testling = createManager();
+            testling->requestVCard(JID("foo@bar.com/baz"));
+                  stanzaChannel->onIQReceived([&](){
+                    return IQ::createError(JID("foo@bar.com/baz"), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID(), ErrorPayload::FeatureNotImplemented);
+            }());
+
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+            CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID("foo@bar.com/baz"), IQ::Get));
+            CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+        }
+
+        void testRequestOwnVCard_ReturnFullVCard() {
+            auto testling = createManager();
             testling->requestVCard(ownJID);
             stanzaChannel->onIQReceived(createOwnVCardResult());
 
@@ -139,8 +201,47 @@ class VCardManagerTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT_EQUAL(std::string("Myself"), ownChanges[0]->getFullName());
         }
 
+        void testRequestOwnVCard_ReturnEmptyVCard() {
+            auto testling = createManager();
+            testling->requestVCard(ownJID);
+            stanzaChannel->onIQReceived([&](){
+                    auto vcard = std::make_shared<VCard>();
+                    return IQ::createResult(JID(), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID(), vcard);
+            }());
+
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+            CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID(), IQ::Get));
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+            CPPUNIT_ASSERT_EQUAL(true, changes[0].second->isEmpty());
+        }
+
+        void testRequestOwnVCard_ReturnItemNotFoundError() {
+            auto testling = createManager();
+            testling->requestVCard(ownJID);
+            stanzaChannel->onIQReceived([&](){
+                    return IQ::createError(JID(), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID(), ErrorPayload::ItemNotFound);
+            }());
+
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+            CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID(), IQ::Get));
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+            CPPUNIT_ASSERT_EQUAL(true, changes[0].second->isEmpty());
+        }
+
+        void testRequestOwnVCard_ReturnFeatureNotImplementedError() {
+            auto testling = createManager();
+            testling->requestVCard(ownJID);
+            stanzaChannel->onIQReceived([&](){
+                    return IQ::createError(JID(), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID(), ErrorPayload::FeatureNotImplemented);
+            }());
+
+            CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+            CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCard>(0, JID(), IQ::Get));
+            CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+        }
+
         void testCreateSetVCardRequest() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             VCard::ref vcard = std::make_shared<VCard>();
             vcard->setFullName("New Name");
             SetVCardRequest::ref request = testling->createSetVCardRequest(vcard);
@@ -154,7 +255,7 @@ class VCardManagerTest : public CppUnit::TestFixture {
         }
 
         void testCreateSetVCardRequest_Error() {
-            std::shared_ptr<VCardManager> testling = createManager();
+            auto testling = createManager();
             VCard::ref vcard = std::make_shared<VCard>();
             vcard->setFullName("New Name");
             SetVCardRequest::ref request = testling->createSetVCardRequest(vcard);
diff --git a/Swiften/VCards/VCardManager.cpp b/Swiften/VCards/VCardManager.cpp
index 95b96fa..9423702 100644
--- a/Swiften/VCards/VCardManager.cpp
+++ b/Swiften/VCards/VCardManager.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -50,10 +50,9 @@ void VCardManager::requestOwnVCard() {
     requestVCard(JID());
 }
 
-
 void VCardManager::handleVCardReceived(const JID& actualJID, VCard::ref vcard, ErrorPayload::ref error) {
     requestedVCards.erase(actualJID);
-    if (!error) {
+    if (!error || (error && error->getCondition() == ErrorPayload::ItemNotFound)) {
         if (!vcard) {
             vcard = VCard::ref(new VCard());
         }
-- 
cgit v0.10.2-6-g49f6


From fed3c93f4efc3a188ee9ef83f509cd1d8a41a8d2 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Thu, 5 Apr 2018 13:54:51 +0200
Subject: Fix convertToWindowsVersion() function to handle more RCs

The new version conversion function allows up to 8 patch releases,
up to 11 RC releases, up to 7 beta releases, and up to 5 alpha
releases.

The script fails hard on invalid input, thus failing the build
when used. It also allows for checking the release tag in the
release process.

Test-Information:

Added unit tests for some critical properties of the conversion.
As previously the minor number of the Windows version triplet
was not used, all new versions using this new scheme are
considered newer, for the same major version.

Change-Id: I53552a72ceaf7fe90f919990bd3d3be45a976c03

diff --git a/BuildTools/SCons/Version.py b/BuildTools/SCons/Version.py
index d34c2a7..f215a5d 100644
--- a/BuildTools/SCons/Version.py
+++ b/BuildTools/SCons/Version.py
@@ -1,4 +1,4 @@
-import subprocess, os, datetime, re, os.path
+import subprocess, os, datetime, re, os.path, sys, unittest
 
 def getGitBuildVersion(root, project) :
     tag = git("describe --tags --exact --match \"" + project + "-*\"", root)
@@ -37,32 +37,165 @@ def getBuildVersion(root, project) :
 
     return datetime.date.today().strftime("%Y%m%d")
 
-def convertToWindowsVersion(version) :
-    version_match = re.match("(\d+)\.(\d+)(.*)", version)
-    major = version_match and int(version_match.group(1)) or 0
-    minor = version_match and int(version_match.group(2)) or 0
-    if version_match and len(version_match.group(3)) == 0 :
-        patch = 60000
-    else :
-        match = re.match("^beta(\d+)(.*)", version_match.group(3))
-        build_string = ""
-        if match :
-            patch = 1000*int(match.group(1))
-            build_string = match.group(2)
-        else :
-            rc_match = re.match("^rc(\d+)(.*)", version_match.group(3))
-            if rc_match :
-                patch = 10000*int(rc_match.group(1))
-                build_string = rc_match.group(2)
-            else :
-                patch = 0
-                alpha_match = re.match("^alpha(.*)", version_match.group(3))
-                if alpha_match :
-                    build_string = alpha_match.group(1)
-
-        if len(build_string) > 0 :
-            build_match = re.match("^-dev(\d+)", build_string)
-            if build_match :
-                patch += int(build_match.group(1))
+# The following conversion allows us to use version tags the format:
+#   major.0
+#   major.0.(0 to 9)
+#
+#   Either from above followed by:
+#   alpha(0 to 4)
+#   beta(0 to 6)
+#   rc(1 to 11)
+#
+#   Followed by an optional -dev(1-65535) for off tag builds.
+def convertToWindowsVersion(version):
+    match = re.match(r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<patch>\d+)?(?:(?P<stage>rc|beta|alpha)(?P<stage_number>\d+)?)?(?:-dev(?P<dev>\d+))?", version)
+    assert(match)
+    major, minor, patch = (0, 0, 0)
 
+    groups = match.groupdict()
+    assert(groups['major'])
+    major = int(groups['major'])
+
+    if groups['minor']:
+        assert(int(groups['minor']) == 0)
+
+    if groups['patch']:
+        assert(0 <= int(groups['patch']) <= 9)
+        minor = int(groups['patch']) * 25
+
+    stage = groups["stage"]
+    if stage:
+        stageNumber = groups['stage_number']
+        if not stageNumber or stageNumber == "":
+            stageNumber = 0
+        else:
+            stageNumber = int(stageNumber)
+
+        if stage == "alpha":
+            assert(0 <= stageNumber <= 4)
+            minor = 1 + stageNumber
+        elif stage == "beta":
+            assert(0 <= stageNumber <= 6)
+            minor = 6 + stageNumber
+        elif stage == "rc":
+            assert(1 <= stageNumber <= 11)
+            minor = 12 + stageNumber
+        else:
+            assert(False)
+    else:
+        minor = minor + 24
+
+    if groups['dev']:
+        patch = 1 + int(groups['dev'])
+
+    # The following constraints are set by Windows Installer framework
+    assert(0 <= major <= 255)
+    assert(0 <= minor <= 255)
+    assert(0 <= patch <= 65535)
     return (major, minor, patch)
+
+# Test Windows version mapping scheme
+class convertToWindowsVersionTest(unittest.TestCase):
+    def testWindowsVersionsAreDescending(self):
+        versionStringsWithOldVersions = [
+            ("5.0rc11", None),
+            ("5.0rc1", None),
+            ("5.0beta6", None),
+            ("5.0alpha4", None),
+            ("5.0alpha2", None),
+            ("5.0alpha", None),
+            ("4.0.9", None),
+            ("4.0.1", None),
+            ("4.0", (4, 0, 60000)),
+            ("4.0rc6", (4, 0, 60000)),
+            ("4.0rc5", (4, 0, 50000)),
+            ("4.0rc4", (4, 0, 40000)),
+            ("4.0rc3", (4, 0, 30000)),
+            ('4.0rc2-dev34', (4, 0, 20034)),
+            ('4.0rc2-dev33', (4, 0, 20033)),
+            ('4.0rc2-dev31', (4, 0, 20031)),
+            ('4.0rc2-dev30', (4, 0, 20030)),
+            ('4.0rc2-dev29', (4, 0, 20029)),
+            ('4.0rc2-dev27', (4, 0, 20027)),
+            ('4.0rc2-dev39', (4, 0, 20039)),
+            ('4.0rc2', (4, 0, 20000)),
+            ('4.0rc1', (4, 0, 10000)),
+            ('4.0beta2-dev203', (4, 0, 2203)),
+            ('4.0beta2-dev195', (4, 0, 2195)),
+            ('4.0beta2-dev177', (4, 0, 2177)),
+            ('4.0beta2-dev171', (4, 0, 2171)),
+            ('4.0beta2-dev154', (4, 0, 2154)),
+            ('4.0beta2-dev150', (4, 0, 2150)),
+            ('4.0beta2-dev142', (4, 0, 2142)),
+            ('4.0beta2-dev140', (4, 0, 2140)),
+            ('4.0beta2-dev133', (4, 0, 2133)),
+            ('4.0beta2-dev118', (4, 0, 2118)),
+            ('4.0beta2-dev112', (4, 0, 2112)),
+            ('4.0beta2-dev93', (4, 0, 2093)),
+            ('4.0beta2-dev80', (4, 0, 2080)),
+            ('4.0beta2-dev72', (4, 0, 2072)),
+            ('4.0beta2-dev57', (4, 0, 2057)),
+            ('4.0beta2-dev44', (4, 0, 2044)),
+            ('4.0beta2-dev38', (4, 0, 2038)),
+            ('4.0beta2-dev29', (4, 0, 2029)),
+            ('4.0beta2-dev15', (4, 0, 2015)),
+            ('4.0beta2', (4, 0, 2000)),
+            ('4.0beta1', (4, 0, 1000)),
+            ('4.0alpha-dev80', (4, 0, 80)),
+            ('4.0alpha-dev50', (4, 0, 50)),
+            ('4.0alpha-dev43', (4, 0, 43)),
+            ('4.0alpha-dev21', (4, 0, 21)),
+            ('3.0', (3, 0, 60000)),
+            ('3.0rc3', (3, 0, 30000)),
+            ('3.0rc2', (3, 0, 20000)),
+            ('3.0rc1', (3, 0, 10000)),
+            ('3.0beta2-dev124', (3, 0, 2124)),
+            ('3.0beta2-dev81', (3, 0, 2081)),
+            ('3.0beta2-dev50', (3, 0, 2050)),
+            ('3.0beta2-dev44', (3, 0, 2044)),
+            ('3.0beta2-dev40', (3, 0, 2040)),
+            ('3.0beta2-dev26', (3, 0, 2026)),
+            ('3.0beta2', (3, 0, 2000)),
+            ('3.0beta1', (3, 0, 1000)),
+            ('3.0alpha-dev529', (3, 0, 529)),
+            ('3.0alpha-dev528', (3, 0, 528)),
+            ('3.0alpha-dev526', (3, 0, 526)),
+            ('3.0alpha-dev524', (3, 0, 524)),
+            ('3.0alpha-dev515', (3, 0, 515)),
+        ]
+        windowsVersionMapping = list(map(lambda (x,y): (x, convertToWindowsVersion(x)), versionStringsWithOldVersions))
+
+    def testThatBetaIsHigherThanAlpha(self):
+        self.assertTrue(convertToWindowsVersion("3.0beta0") > convertToWindowsVersion("3.0alpha0"))
+        self.assertTrue(convertToWindowsVersion("3.0beta6") > convertToWindowsVersion("3.0alpha1"))
+        self.assertTrue(convertToWindowsVersion("3.0beta6") > convertToWindowsVersion("3.0alpha4"))
+
+    def testThatRcIsHigherThanAlphaAndBeta(self):
+        self.assertTrue(convertToWindowsVersion("3.0rc11") > convertToWindowsVersion("3.0alpha0"))
+        self.assertTrue(convertToWindowsVersion("3.0rc11") > convertToWindowsVersion("3.0alpha4"))
+        self.assertTrue(convertToWindowsVersion("3.0rc1") > convertToWindowsVersion("3.0alpha0"))
+        self.assertTrue(convertToWindowsVersion("3.0rc1") > convertToWindowsVersion("3.0alpha4"))
+        self.assertTrue(convertToWindowsVersion("3.0rc11") > convertToWindowsVersion("3.0beta0"))
+        self.assertTrue(convertToWindowsVersion("3.0rc11") > convertToWindowsVersion("3.0beta6"))
+        self.assertTrue(convertToWindowsVersion("3.0rc1") > convertToWindowsVersion("3.0beta0"))
+        self.assertTrue(convertToWindowsVersion("3.0rc1") > convertToWindowsVersion("3.0beta6"))
+
+    def testThatStableIsHigherThanAlphaAndBetaAndRc(self):
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0alpha0"))
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0alpha4"))
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0alpha0"))
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0alpha4"))
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0beta0"))
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0beta6"))
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0rc1"))
+        self.assertTrue(convertToWindowsVersion("3.0") > convertToWindowsVersion("3.0rc11"))
+
+if __name__ == '__main__':
+    if len(sys.argv) == 1:
+        unittest.main()
+    elif len(sys.argv) == 2:
+        print convertToWindowsVersion(sys.argv[1])
+        sys.exit(0)
+    else:
+        print "Error: Simply run the script without arguments or pass a single argument."
+        sys.exit(-1)
diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md
index 3516022..d43a6db 100644
--- a/Swift/ChangeLog.md
+++ b/Swift/ChangeLog.md
@@ -1,3 +1,7 @@
+4.0.2 (2018-04-05)
+------------------
+- Fix versioning issue in Windows Installer process
+
 4.0.1 (2018-03-28)
 ------------------
 - Allow setting vCard on servers that do not return an empty vCard on fresh accounts
-- 
cgit v0.10.2-6-g49f6


From 70e58211a49782e449eacbef48bd076d24fed57e Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Tue, 10 Apr 2018 11:54:11 +0200
Subject: Update Debian changelog

Closed two further bugs from the Debian bug tracker in the
changelog entry for 4.0.2.

Test-Information:

None.

Change-Id: I74dd7350990ddd1627b2b9c64b322ed84f3a6186

diff --git a/Swift/Packaging/Debian/changelog.debian-unstable b/Swift/Packaging/Debian/changelog.debian-unstable
index ca9ffec..a6b7606 100644
--- a/Swift/Packaging/Debian/changelog.debian-unstable
+++ b/Swift/Packaging/Debian/changelog.debian-unstable
@@ -1,3 +1,21 @@
+swift-im (4.0.2-1) UNRELEASED; urgency=medium
+
+  * New chat theme including a new font
+  * Support for message carbons (XEP-0280)
+  * Enabled trellis mode as a default feature, allowing several tiled chats windows to be shown at once
+  * Redesigned keyword highlighting
+  * Improve date formatting
+  * Fix Last Message Correction in multi client scenarios
+  * Fix UI layout issue for translations that require right-to-left (RTL) layout
+  * Fix UX issues in trellis mode
+  * Improvements to font size handling in the chat theme
+  * Add AppImage for Linux 64-bit as a supported platform
+  * Improved spell checker support on Linux
+  * Allow setting vCard on servers that do not return an empty vCard on fresh accounts
+  * And assorted smaller features and usability enhancements. Closes: 840151, 889062
+
+ -- Kevin Smith <kevin@kismith.co.uk>  Tue, 10 Apr 2018 10:46:13 +0200
+
 swift-im (3.0.4-1) unstable; urgency=low
 
   * New upstream release
-- 
cgit v0.10.2-6-g49f6


From 1d18148c86377787a8c77042b12ea66f20cb2ca9 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Thu, 21 Jun 2018 13:04:56 +0200
Subject: Add missing include for QAbstractItemModel

This fixes building Swift with Qt 5.11.

Test-Information:

Builds and tests pass on macOS 10.13.5 with Qt 5.11.0.

Change-Id: I1be2cd081d8a520ec38ab7cca5ada0d7fc39b777

diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
index 0714ac1..fe536ab 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h
+++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -8,6 +8,7 @@
 
 #include <set>
 
+#include <QAbstractItemModel>
 #include <QWizard>
 
 #include <Swiften/Base/Override.h>
-- 
cgit v0.10.2-6-g49f6


From 1632f62222bb10516b2f85249083c0fb30c7d1b3 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Tue, 17 Jul 2018 15:58:05 +0100
Subject: Don't crash on missing bookmark result

Test-Information:
Before the patch, boom, after the patch, no boom.
(No boom today. Boom tomorrow. There's always a boom tomorrow)

Change-Id: Id454d7b0d0cd05774d0f1ee0b3cb77057371c459

diff --git a/Swiften/MUC/MUCBookmarkManager.cpp b/Swiften/MUC/MUCBookmarkManager.cpp
index 9f8ae77..511c88a 100644
--- a/Swiften/MUC/MUCBookmarkManager.cpp
+++ b/Swiften/MUC/MUCBookmarkManager.cpp
@@ -25,7 +25,7 @@ MUCBookmarkManager::MUCBookmarkManager(IQRouter* iqRouter) {
 }
 
 void MUCBookmarkManager::handleBookmarksReceived(std::shared_ptr<Storage> payload, ErrorPayload::ref error) {
-    if (error) {
+    if (error || !payload) {
         return;
     }
 
-- 
cgit v0.10.2-6-g49f6


From 6874d64ed2684d83cb3e340f58f6c8c5089aa857 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Thu, 3 Jan 2019 14:16:13 +0100
Subject: Update for Debian

Fix UTF-8 handling issues in
BuildTools/SCons/Tools/textfile.py .

Test-Information:

Tested that ./scons test=unit Swift works on Debain
Unstable (sid) and macOS 10.14.2. Tested that
build_for_debian.sh runs successfully on latest Debian
Unstable (sid).

Change-Id: I29d8c97ce3b4eb3e4fd680bdc814fb0c911262ff

diff --git a/BuildTools/SCons/Tools/textfile.py b/BuildTools/SCons/Tools/textfile.py
index 89f8963..cc58666 100644
--- a/BuildTools/SCons/Tools/textfile.py
+++ b/BuildTools/SCons/Tools/textfile.py
@@ -63,7 +63,7 @@ def _do_subst(node, subs):
     then all instances of %VERSION% in the file will be replaced with
     1.2345 and so forth.
     """
-    contents = node.get_text_contents()
+    contents = node.get_contents().decode('utf-8')
     if not subs: return contents
     for (k,v) in subs:
         contents = re.sub(k, v, contents)
@@ -113,7 +113,7 @@ def _action(target, source, env):
     lsep = None
     for s in source:
         if lsep: fd.write(lsep)
-        fd.write(_do_subst(s, subs))
+        fd.write(_do_subst(s, subs).encode("utf-8"))
         lsep = linesep
     fd.close()
 
diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md
index d43a6db..9152b50 100644
--- a/Swift/ChangeLog.md
+++ b/Swift/ChangeLog.md
@@ -1,3 +1,7 @@
+4.0.3 (2019-01-03)
+------------------
+- Fix handling of empty bookmark responses
+
 4.0.2 (2018-04-05)
 ------------------
 - Fix versioning issue in Windows Installer process
diff --git a/Swift/Packaging/Debian/changelog.debian-unstable b/Swift/Packaging/Debian/changelog.debian-unstable
index a6b7606..f2bf2c5 100644
--- a/Swift/Packaging/Debian/changelog.debian-unstable
+++ b/Swift/Packaging/Debian/changelog.debian-unstable
@@ -1,4 +1,4 @@
-swift-im (4.0.2-1) UNRELEASED; urgency=medium
+swift-im (4.0.3-1) UNRELEASED; urgency=medium
 
   * New chat theme including a new font
   * Support for message carbons (XEP-0280)
@@ -14,7 +14,7 @@ swift-im (4.0.2-1) UNRELEASED; urgency=medium
   * Allow setting vCard on servers that do not return an empty vCard on fresh accounts
   * And assorted smaller features and usability enhancements. Closes: 840151, 889062
 
- -- Kevin Smith <kevin@kismith.co.uk>  Tue, 10 Apr 2018 10:46:13 +0200
+ -- Kevin Smith <kevin@kismith.co.uk>  Thu, 03 Jan 2019 13:59:07 +0100
 
 swift-im (3.0.4-1) unstable; urgency=low
 
diff --git a/Swift/Packaging/Debian/debian/copyright b/Swift/Packaging/Debian/debian/copyright
index a0c2c79..9219930 100644
--- a/Swift/Packaging/Debian/debian/copyright
+++ b/Swift/Packaging/Debian/debian/copyright
@@ -3,7 +3,7 @@ with help from Olly Betts <olly@survex.com>.
 
 The upstream sources were obtained from http://swift.im.
 
-Copyright (C) 2010-2016 Isode Limited.
+Copyright (C) 2010-2019 Isode Limited.
 Licensed under the GNU General Public License.
 See /usr/share/common-licenses/GPL-3 for the full license.
 
-- 
cgit v0.10.2-6-g49f6