diff options
-rw-r--r-- | BuildTools/SCons/SConscript.boot | 3 | ||||
-rw-r--r-- | BuildTools/SCons/SConstruct | 9 | ||||
-rw-r--r-- | BuildTools/SCons/Tools/AppBundle.py | 7 | ||||
-rw-r--r-- | DEVELOPMENT.md | 19 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/AutoUpdater.h | 13 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdater.h | 7 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdater.mm | 31 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h | 35 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.mm | 57 | ||||
-rw-r--r-- | SwifTools/SConscript | 2 | ||||
-rw-r--r-- | Swift/QtUI/CocoaUIHelpers.h | 1 | ||||
-rw-r--r-- | Swift/QtUI/CocoaUIHelpers.mm | 4 | ||||
-rw-r--r-- | Swift/QtUI/QtSwift.cpp | 32 | ||||
-rw-r--r-- | Swift/QtUI/QtSwift.h | 8 | ||||
-rw-r--r-- | Swift/QtUI/SConscript | 5 |
15 files changed, 217 insertions, 16 deletions
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot index 8379a58..d6527f3 100644 --- a/BuildTools/SCons/SConscript.boot +++ b/BuildTools/SCons/SConscript.boot @@ -110,6 +110,9 @@ vars.Add("codesign_identity", "macOS code signing identity to be passed to codes vars.Add("signtool_key_pfx", "The keyfile (.pfx) that will be used to sign the Windows installer.", None) vars.Add("signtool_timestamp_url", "The timestamp server that will be queried for a signed time stamp in the signing process.", None) +# Automatic Software Update Options +vars.Add(PathVariable("sparkle_public_dsa_key", "Optional path to a public DSA key used to verify Sparkle software updates. Without specifiying this option, the app needs to be code signed for Sparkle to work.", None, PathVariable.PathIsFile)) + ################################################################################ # Set up default build & configure environment diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct index b5757b8..2da3787 100644 --- a/BuildTools/SCons/SConstruct +++ b/BuildTools/SCons/SConstruct @@ -289,7 +289,7 @@ if env.get("try_gconf", True) and env["PLATFORM"] != "win32" and env["PLATFORM"] env["HAVE_SPARKLE"] = 0 if env["PLATFORM"] == "darwin" : sparkle_flags = { - "FRAMEWORKPATH": ["/Library/Frameworks"], + "FRAMEWORKPATH": ["3rdParty/Sparkle/Sparkle-1.14.0"], "FRAMEWORKS": ["Sparkle"] } sparkle_env = conf_env.Clone() @@ -298,9 +298,14 @@ if env["PLATFORM"] == "darwin" : if conf.CheckObjCHeader("Sparkle/Sparkle.h") : env["HAVE_SPARKLE"] = 1 env["SPARKLE_FLAGS"] = sparkle_flags - env["SPARKLE_FRAMEWORK"] = "/Library/Frameworks/Sparkle.framework" + env["SPARKLE_FRAMEWORK"] = Dir("../../3rdParty/Sparkle/Sparkle-1.14.0/Sparkle.framework") conf.Finish() + if env.get("sparkle_public_dsa_key", None) != None : + env["SWIFT_SPARKLE_PUBLIC_DSA_KEY"] = File(env.get("sparkle_public_dsa_key")) + else : + env["SWIFT_SPARKLE_PUBLIC_DSA_KEY"] = None + # Growl env["HAVE_GROWL"] = 0 if env["PLATFORM"] == "darwin" : diff --git a/BuildTools/SCons/Tools/AppBundle.py b/BuildTools/SCons/Tools/AppBundle.py index fda3484..5f19898 100644 --- a/BuildTools/SCons/Tools/AppBundle.py +++ b/BuildTools/SCons/Tools/AppBundle.py @@ -1,7 +1,7 @@ import SCons.Util, os.path def generate(env) : - def createAppBundle(env, bundle, version = "1.0", resources = [], frameworks = [], info = {}, handlesXMPPURIs = False) : + def createAppBundle(env, bundle, version = "1.0", resources = [], frameworks = [], info = {}, handlesXMPPURIs = False, sparklePublicDSAKey = None) : bundleDir = bundle + ".app" bundleContentsDir = bundleDir + "/Contents" resourcesDir = bundleContentsDir + "/Resources" @@ -44,6 +44,11 @@ def generate(env) : </array> </dict> </array>\n""" + + if sparklePublicDSAKey : + plist += "<key>SUPublicDSAKeyFile</key>" + plist += "<string>" + sparklePublicDSAKey.name.encode("utf-8") + "</string>" + env.Install(resourcesDir, sparklePublicDSAKey) plist += """</dict> </plist> """ diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2b8ca99..3d9c1a7 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -68,6 +68,25 @@ Notes: paths - Currently only 32-bit builds of the Swift client are supported +## Automatic Software Updates +Automatic software updates allow distribution of updates directly to the end users. +This is useful for general feature updates, bug fixes and especially for security +updates. + +### Automatic Software Updates for Mac OS X using Sparkle +Swift supports integration with the software update framework [Sparkle](https://sparkle-project.org/) on OS X. For security reasons, +Sparkle requires the application to be either code-signed or a bundled public DSA +key. In case you do not code-sign, you can provide the path to the public DSA key +to be bundled with the application bundle via the `sparkle_public_dsa_key` SCons +argument. + +To build with Sparkle support, simply download Sparkle-1.14.0 and extract it to +`3rdParty/Sparkle/Sparkle-1.14.0`. SCons will pick it up during configuration +and build Swift with Sparkle support. + +The appcast URL is specified as a compile time preprocessor variable `SWIFT_APPCAST_URL` +in `Swift/QtUI/QtSwift.cpp` + ## Building Swiften for Android This section describes how to build Swiften for Android. It can then be used from any Android native code. This guide has been tested on OS X and Linux. diff --git a/SwifTools/AutoUpdater/AutoUpdater.h b/SwifTools/AutoUpdater/AutoUpdater.h index dec85c9..ed53e11 100644 --- a/SwifTools/AutoUpdater/AutoUpdater.h +++ b/SwifTools/AutoUpdater/AutoUpdater.h @@ -1,16 +1,27 @@ /* - * Copyright (c) 2010 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once +#include <boost/signals2.hpp> + namespace Swift { class AutoUpdater { public: virtual ~AutoUpdater(); virtual void checkForUpdates() = 0; + virtual bool recommendRestartToUpdate() = 0; + + public: + /** + * Emit this signal if a new version of the software has been downloaded + * and the user needs to be notified so they can quit the app and start + * the newer version. + */ + boost::signals2::signal<void()> onSuggestRestartToUserToUpdate; }; } diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.h b/SwifTools/AutoUpdater/SparkleAutoUpdater.h index 95ca35e..c3394f7 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.h +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.h @@ -11,12 +11,19 @@ #include <SwifTools/AutoUpdater/AutoUpdater.h> namespace Swift { + /** + * @brief The SparkleAutoUpdater class provides integration with Sparkle. + * This enables automatic silent background updates. If using this in Qt you + * need to emit a NSApplicationWillTerminateNotification before you quit + * the application. + */ class SparkleAutoUpdater : public AutoUpdater { public: SparkleAutoUpdater(const std::string& url); ~SparkleAutoUpdater(); void checkForUpdates(); + bool recommendRestartToUpdate(); private: class Private; diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm index bcd1388..7e06b2f 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm @@ -1,13 +1,24 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #include <SwifTools/AutoUpdater/SparkleAutoUpdater.h> #include <Cocoa/Cocoa.h> #include <Sparkle/Sparkle.h> +#include <SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h> +#include <SwifTools/Cocoa/CocoaUtil.h> + namespace Swift { class SparkleAutoUpdater::Private { public: SUUpdater* updater; + boost::intrusive_ptr<SparkleAutoUpdaterDelegate> delegate; + bool restartToUpdate = false; }; SparkleAutoUpdater::SparkleAutoUpdater(const std::string& url) { @@ -15,20 +26,36 @@ SparkleAutoUpdater::SparkleAutoUpdater(const std::string& url) { d->updater = [SUUpdater sharedUpdater]; [d->updater retain]; + + d->delegate = boost::intrusive_ptr<SparkleAutoUpdaterDelegate>([[SparkleAutoUpdaterDelegate alloc] init], false); + [d->delegate.get() setUpdateDownloadFinished: [&](){ + d->restartToUpdate = true; + onSuggestRestartToUserToUpdate(); + }]; + [d->updater setDelegate: d->delegate.get()]; + [d->updater setAutomaticallyChecksForUpdates: true]; + // Automatically check for an update after a day. + [d->updater setUpdateCheckInterval: 86400]; + [d->updater setAutomaticallyDownloadsUpdates: true]; - NSURL* nsurl = [NSURL URLWithString: - [NSString stringWithUTF8String: url.c_str()]]; + NSURL* nsurl = [NSURL URLWithString: std2NSString(url)]; [d->updater setFeedURL: nsurl]; } SparkleAutoUpdater::~SparkleAutoUpdater() { [d->updater release]; delete d; + SWIFT_LOG(warning) << std::endl; } void SparkleAutoUpdater::checkForUpdates() { + //[d->updater resetUpdateCycle]; // This is useful for testing to force a check ot start. [d->updater checkForUpdatesInBackground]; } +bool SparkleAutoUpdater::recommendRestartToUpdate() { + return d->restartToUpdate; +} + } diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h b/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h new file mode 100644 index 0000000..8f408de --- /dev/null +++ b/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <functional> + +#import <Cocoa/Cocoa.h> + +#import <Sparkle/Sparkle.h> + +namespace Swift { + class SparkleAutoUpdater; +} + +@interface SparkleAutoUpdaterDelegate : NSObject<SUUpdaterDelegate> +@property (atomic) std::function< void ()> updateDownloadFinished; + +- (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast; + +- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update; + +- (id <SUVersionComparison>)versionComparatorForUpdater:(SUUpdater *)updater; + +- (void)updaterDidNotFindUpdate:(SUUpdater *)update; + +- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update; + +- (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)item immediateInstallationInvocation:(NSInvocation *)invocation; + +- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error; +@end diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.mm b/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.mm new file mode 100644 index 0000000..6e832ba --- /dev/null +++ b/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.mm @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#import "SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h" + +#include <string> + +#include <Swiften/Base/Log.h> + +#include <SwifTools/Cocoa/CocoaUtil.h> + +using namespace Swift; + +@implementation SparkleAutoUpdaterDelegate + +@synthesize updateDownloadFinished; + +- (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast { + (void)updater; + (void)appcast; +} + +- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update { + (void)updater; + (void)update; +} + +- (id <SUVersionComparison>)versionComparatorForUpdater:(SUUpdater *)updater { + (void)updater; + return nil; +} + +- (void)updaterDidNotFindUpdate:(SUUpdater *)updater { + (void)updater; +} + +- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update { + (void)updater; + (void)update; +} + +- (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)item immediateInstallationInvocation:(NSInvocation *)invocation { + (void)updater; + (void)item; + (void)invocation; + updateDownloadFinished(); +} + +- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error { + (void)updater; + SWIFT_LOG(error) << ns2StdString([error localizedDescription]) << std::endl; +} + +@end diff --git a/SwifTools/SConscript b/SwifTools/SConscript index aa6d47e..dec343e 100644 --- a/SwifTools/SConscript +++ b/SwifTools/SConscript @@ -50,7 +50,7 @@ if env["SCONS_STAGE"] == "build" : if swiftools_env.get("HAVE_SPARKLE", 0) : swiftools_env.UseFlags(swiftools_env["SPARKLE_FLAGS"]) swiftools_env.Append(CPPDEFINES = ["HAVE_SPARKLE"]) - sources += ["AutoUpdater/SparkleAutoUpdater.mm"] + sources += ["AutoUpdater/SparkleAutoUpdater.mm", "AutoUpdater/SparkleAutoUpdaterDelegate.mm"] if swiftools_env["PLATFORM"] == "win32" : sources += ["Idle/WindowsIdleQuerier.cpp"] diff --git a/Swift/QtUI/CocoaUIHelpers.h b/Swift/QtUI/CocoaUIHelpers.h index 58cd539..8d96bd9 100644 --- a/Swift/QtUI/CocoaUIHelpers.h +++ b/Swift/QtUI/CocoaUIHelpers.h @@ -21,6 +21,7 @@ namespace Swift { class CocoaUIHelpers { public: static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); + static void sendCocoaApplicationWillTerminateNotification(); }; } diff --git a/Swift/QtUI/CocoaUIHelpers.mm b/Swift/QtUI/CocoaUIHelpers.mm index c876312..3ffa72c 100644 --- a/Swift/QtUI/CocoaUIHelpers.mm +++ b/Swift/QtUI/CocoaUIHelpers.mm @@ -46,4 +46,8 @@ void CocoaUIHelpers::displayCertificateChainAsSheet(QWidget* parent, const std:: [certificates release]; } +void CocoaUIHelpers::sendCocoaApplicationWillTerminateNotification() { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NSApplicationWillTerminateNotification" object:nil]; +} + } diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index 5d05a3d..d8dfac4 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -77,12 +77,16 @@ #include <Swift/QtUI/QtDBUSURIHandler.h> #endif +#if defined(SWIFTEN_PLATFORM_MACOSX) +#include <Swift/QtUI/CocoaUIHelpers.h> +#endif + namespace Swift{ #if defined(SWIFTEN_PLATFORM_MACOSX) -//#define SWIFT_APPCAST_URL "http://swift.im/appcast/swift-mac-dev.xml" +#define SWIFT_APPCAST_URL "https://swift.im/appcast/swift-mac-dev.xml" #else -//#define SWIFT_APPCAST_URL "" +#define SWIFT_APPCAST_URL "" #endif po::options_description QtSwift::getOptionsDescription() { @@ -277,12 +281,14 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa mainControllers_.push_back(mainController); } + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(handleAboutToQuit())); - // PlatformAutoUpdaterFactory autoUpdaterFactory; - // if (autoUpdaterFactory.isSupported()) { - // autoUpdater_ = autoUpdaterFactory.createAutoUpdater(SWIFT_APPCAST_URL); - // autoUpdater_->checkForUpdates(); - // } + PlatformAutoUpdaterFactory autoUpdaterFactory; + if (autoUpdaterFactory.isSupported()) { + autoUpdater_ = autoUpdaterFactory.createAutoUpdater(SWIFT_APPCAST_URL); + autoUpdater_->checkForUpdates(); + autoUpdater_->onSuggestRestartToUserToUpdate.connect(boost::bind(&QtSwift::handleRecommendRestartToInstallUpdate, this)); + } } QtSwift::~QtSwift() { @@ -312,4 +318,16 @@ QtSwift::~QtSwift() { delete applicationPathProvider_; } +void QtSwift::handleAboutToQuit() { +#if defined(SWIFTEN_PLATFORM_MACOSX) + // This is required so Sparkle knows about the application shutting down + // and can update the application in background. + CocoaUIHelpers::sendCocoaApplicationWillTerminateNotification(); +#endif +} + +void QtSwift::handleRecommendRestartToInstallUpdate() { + notifier_->showMessage(Notifier::SystemMessage, Q2PSTRING(tr("Swift Update Available")), Q2PSTRING(tr("Restart Swift now or later to update to the new Swift version")), "", [](){}); +} + } diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index 9932545..64b79b8 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -59,9 +59,15 @@ namespace Swift { QtSwift(const po::variables_map& options); static po::options_description getOptionsDescription(); ~QtSwift(); + + private slots: + void handleAboutToQuit(); + void handleRecommendRestartToInstallUpdate(); + private: XMLSettingsProvider* loadSettingsFile(const QString& fileName); void loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons); + private: QtEventLoop clientMainThreadCaller_; PlatformTLSFactories tlsFactories_; diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 2d01672..fd47dd4 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -79,6 +79,9 @@ if env["PLATFORM"] == "win32" : myenv.Append(LIBS = "Cryptui") myenv.Append(CPPDEFINES = "HAVE_SCHANNEL") +if env["PLATFORM"] == "darwin" and env["HAVE_SPARKLE"] : + myenv.Append(LINKFLAGS = ["-Wl,-rpath,@loader_path/../Frameworks"]) + myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateQRCTheme(myenv.Dir("#/Swift/resources/themes/Default"), "Default"))) sources = [ @@ -364,7 +367,7 @@ if env["PLATFORM"] == "darwin" : if env["HAVE_GROWL"] : frameworks.append(env["GROWL_FRAMEWORK"]) commonResources[""] = commonResources.get("", []) + ["#/Swift/resources/MacOSX/Swift.icns"] - app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True) + app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True, sparklePublicDSAKey = myenv["SWIFT_SPARKLE_PUBLIC_DSA_KEY"]) if env["DIST"] : myenv.Command(["#/Packages/Swift/Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR " + "\"$CODE_SIGN_IDENTITY\""]) dsym = myenv.Command(["Swift-${SWIFT_VERSION}.dSYM"], ["Swift"], ["dsymutil -o ${TARGET} ${SOURCE}"]) |