diff options
Diffstat (limited to 'SwifTools/AutoUpdater')
-rw-r--r-- | SwifTools/AutoUpdater/AutoUpdater.cpp | 6 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/AutoUpdater.h | 41 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.cpp | 16 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h | 18 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdater.h | 40 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdater.mm | 150 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h | 37 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.mm | 79 |
8 files changed, 333 insertions, 54 deletions
diff --git a/SwifTools/AutoUpdater/AutoUpdater.cpp b/SwifTools/AutoUpdater/AutoUpdater.cpp index e424f3b..342b1d9 100644 --- a/SwifTools/AutoUpdater/AutoUpdater.cpp +++ b/SwifTools/AutoUpdater/AutoUpdater.cpp @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <SwifTools/AutoUpdater/AutoUpdater.h> diff --git a/SwifTools/AutoUpdater/AutoUpdater.h b/SwifTools/AutoUpdater/AutoUpdater.h index 77e0045..fdd3a91 100644 --- a/SwifTools/AutoUpdater/AutoUpdater.h +++ b/SwifTools/AutoUpdater/AutoUpdater.h @@ -1,16 +1,41 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <string> + +#include <boost/signals2.hpp> + namespace Swift { - class AutoUpdater { - public: - virtual ~AutoUpdater(); + class AutoUpdater { + public: + enum class State { + NotCheckedForUpdatesYet, + NoUpdateAvailable, + CheckingForUpdate, + ErrorCheckingForUpdate, + DownloadingUpdate, + RestartToInstallUpdate + }; + + public: + virtual ~AutoUpdater(); + + virtual void setAppcastFeed(const std::string& appcastFeed) = 0; + virtual void checkForUpdates() = 0; + virtual State getCurrentState() = 0; + virtual bool applicationInstallationLocationWritable() = 0; - virtual void checkForUpdates() = 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(State)> onUpdateStateChanged; + }; } diff --git a/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.cpp b/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.cpp index d2e89ac..9ae8c09 100644 --- a/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.cpp +++ b/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.cpp @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h> @@ -16,18 +16,18 @@ namespace Swift { bool PlatformAutoUpdaterFactory::isSupported() const { #ifdef HAVE_SPARKLE - return true; + return true; #else - return false; + return false; #endif } AutoUpdater* PlatformAutoUpdaterFactory::createAutoUpdater(const std::string& appcastURL) { #ifdef HAVE_SPARKLE - return new SparkleAutoUpdater(appcastURL); + return new SparkleAutoUpdater(appcastURL); #else - (void) appcastURL; - return NULL; + (void) appcastURL; + return nullptr; #endif } diff --git a/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h b/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h index 59df238..9942d6a 100644 --- a/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h +++ b/SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h @@ -1,18 +1,18 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <string> namespace Swift { - class AutoUpdater; + class AutoUpdater; - class PlatformAutoUpdaterFactory { - public: - bool isSupported() const; + class PlatformAutoUpdaterFactory { + public: + bool isSupported() const; - AutoUpdater* createAutoUpdater(const std::string& appcastURL); - }; + AutoUpdater* createAutoUpdater(const std::string& appcastURL); + }; } diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.h b/SwifTools/AutoUpdater/SparkleAutoUpdater.h index f367945..26e08da 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.h +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.h @@ -1,24 +1,38 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <memory> #include <string> + #include <SwifTools/AutoUpdater/AutoUpdater.h> namespace Swift { - class SparkleAutoUpdater : public AutoUpdater { - public: - SparkleAutoUpdater(const std::string& url); - ~SparkleAutoUpdater(); + /** + * @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& appcastFeed); + ~SparkleAutoUpdater() override; + + void setAppcastFeed(const std::string& appcastFeed) override; + void checkForUpdates() override; + State getCurrentState() override; + bool applicationInstallationLocationWritable() override; + + private: + void setCurrentState(State updatedState); - void checkForUpdates(); - - private: - class Private; - Private* d; - }; + private: + class Private; + const std::unique_ptr<Private> d; + }; } diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm index c35abc8..274ab3c 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm @@ -1,34 +1,158 @@ +/* + * Copyright (c) 2016-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #include <SwifTools/AutoUpdater/SparkleAutoUpdater.h> + +#include <boost/filesystem.hpp> +#include <boost/filesystem/fstream.hpp> + #include <Cocoa/Cocoa.h> #include <Sparkle/Sparkle.h> +#include <Swiften/Base/Log.h> + +#include <SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h> +#include <SwifTools/Cocoa/CocoaUtil.h> + namespace Swift { class SparkleAutoUpdater::Private { - public: - SUUpdater* updater; + public: + SUUpdater* updater; + boost::intrusive_ptr<SparkleAutoUpdaterDelegate> delegate; + State currentState = State::NotCheckedForUpdatesYet; }; -SparkleAutoUpdater::SparkleAutoUpdater(const std::string& url) { - d = new Private; +SparkleAutoUpdater::SparkleAutoUpdater(const std::string& appcastFeed) : d(new Private()) { + d->updater = [SUUpdater sharedUpdater]; + [d->updater retain]; - d->updater = [SUUpdater sharedUpdater]; - [d->updater retain]; - [d->updater setAutomaticallyChecksForUpdates: true]; + d->delegate = boost::intrusive_ptr<SparkleAutoUpdaterDelegate>([[SparkleAutoUpdaterDelegate alloc] init], false); + [d->delegate.get() setOnNewUpdateState: [&](AutoUpdater::State updatedState){ + setCurrentState(updatedState); + }]; + [d->updater setDelegate: d->delegate.get()]; - NSURL* nsurl = [NSURL URLWithString: - [NSString stringWithUTF8String: url.c_str()]]; - [d->updater setFeedURL: nsurl]; + [d->updater setAutomaticallyChecksForUpdates: true]; + // Automatically check for an update after a day. + [d->updater setUpdateCheckInterval: 86400]; + + auto canDoSilentUpdates = applicationInstallationLocationWritable(); + [d->updater setAutomaticallyDownloadsUpdates: canDoSilentUpdates]; + + SWIFT_LOG(debug) << (canDoSilentUpdates ? + "The current running user has enough permissions to do a silent update." : + "The current running user has insufficient permissions to do a silent update."); + + setAppcastFeed(appcastFeed); } SparkleAutoUpdater::~SparkleAutoUpdater() { - [d->updater release]; - delete d; + [d->updater release]; +} + +void SparkleAutoUpdater::setAppcastFeed(const std::string& appcastFeed) { + NSURL* nsurl = [NSURL URLWithString: std2NSString(appcastFeed)]; + [d->updater setFeedURL: nsurl]; } void SparkleAutoUpdater::checkForUpdates() { - [d->updater checkForUpdatesInBackground]; + if (!(getCurrentState() == State::CheckingForUpdate || + getCurrentState() == State::DownloadingUpdate || + getCurrentState() == State::RestartToInstallUpdate)) { + setCurrentState(State::CheckingForUpdate); + [d->updater resetUpdateCycle]; + [d->updater checkForUpdatesInBackground]; + } +} + +void SparkleAutoUpdater::setCurrentState(AutoUpdater::State updatedState) { + d->currentState = updatedState; + onUpdateStateChanged(d->currentState); +} + +AutoUpdater::State SparkleAutoUpdater::getCurrentState() { + return d->currentState; +} + +bool Swift::SparkleAutoUpdater::applicationInstallationLocationWritable() { + auto bundlePath = ns2StdString([[NSBundle mainBundle] bundlePath]); + + auto createTemporaryFile = [](const boost::filesystem::path& parentFolder) { + boost::optional<boost::filesystem::path> tempFilePath; + boost::system::error_code error; + + if (!boost::filesystem::is_directory(parentFolder, error) || error) { + return tempFilePath; + } + auto uniquePath = boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%", error); + if (error) { + return tempFilePath; + } + auto testFilePath = parentFolder / uniquePath; + + boost::filesystem::ofstream testFile(testFilePath, std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc); + if (testFile) { + testFile.close(); + tempFilePath = testFilePath; + } + + return tempFilePath; + }; + + auto isDirectoryWritable = [&](const boost::filesystem::path& path) { + auto bundleTestFilePath = createTemporaryFile(path); + if (!bundleTestFilePath) { + return false; + } + + boost::system::error_code error; + if (!boost::filesystem::remove(bundleTestFilePath.get(), error) || error) { + // File did not exist in the first place or error while removing it. + return false; + } + + return true; + }; + + auto applyMatchingPermissions = [](const boost::filesystem::path& permissionsFrom, const boost::filesystem::path& applyTo) { + auto permissions = boost::filesystem::status(permissionsFrom).permissions(); + + boost::system::error_code error; + boost::filesystem::permissions(applyTo, permissions, error); + + return !error; + }; + + auto canChangePermissionsOnTemporaryFile = [&](const boost::filesystem::path& pathToCreateTemporaryFileUnder, const boost::filesystem::path& pathToTakePermissionsFrom) { + auto temporaryFilePath = createTemporaryFile(pathToCreateTemporaryFileUnder); + if (!temporaryFilePath) { + return false; + } + + boost::system::error_code error; + auto fileExists = boost::filesystem::exists(temporaryFilePath.get(), error); + if (!fileExists || error) { + return false; + } + + auto successfullPermissionCopy = applyMatchingPermissions(pathToTakePermissionsFrom, temporaryFilePath.get()); + + boost::filesystem::remove(temporaryFilePath.get(), error); + + return successfullPermissionCopy; + }; + + auto bundleBoostPath = boost::filesystem::path(bundlePath); + if (!isDirectoryWritable(bundleBoostPath.parent_path()) || !isDirectoryWritable(bundleBoostPath)) { + return false; + } + + return canChangePermissionsOnTemporaryFile(bundleBoostPath.parent_path(), bundleBoostPath); } } diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h b/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h new file mode 100644 index 0000000..4aa236b --- /dev/null +++ b/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <functional> + +#import <Cocoa/Cocoa.h> + +#import <Sparkle/Sparkle.h> + +#include <SwifTools/AutoUpdater/AutoUpdater.h> + +@interface SparkleAutoUpdaterDelegate : NSObject<SUUpdaterDelegate> +@property (atomic) std::function< void (Swift::AutoUpdater::State)> onNewUpdateState; + +- (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 willDownloadUpdate:(SUAppcastItem *)item withRequest:(NSMutableURLRequest *)request; + +- (void)updater:(SUUpdater *)updater failedToDownloadUpdate:(SUAppcastItem *)item error:(NSError *)error; + +- (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..b9294d9 --- /dev/null +++ b/SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.mm @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016-2019 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 onNewUpdateState; + +- (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast { + (void)updater; + (void)appcast; + onNewUpdateState(AutoUpdater::State::DownloadingUpdate); +} + +- (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; + onNewUpdateState(AutoUpdater::State::NoUpdateAvailable); +} + +- (void)updater:(SUUpdater *)updater willDownloadUpdate:(SUAppcastItem *)item withRequest:(NSMutableURLRequest *)request { + (void)updater; + (void)item; + (void)request; + onNewUpdateState(AutoUpdater::State::DownloadingUpdate); +} + +- (void)updater:(SUUpdater *)updater failedToDownloadUpdate:(SUAppcastItem *)item error:(NSError *)error { + (void)updater; + (void)item; + SWIFT_LOG(error) << ns2StdString([error localizedDescription]); + onNewUpdateState(AutoUpdater::State::ErrorCheckingForUpdate); +} + +- (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; + onNewUpdateState(AutoUpdater::State::RestartToInstallUpdate); +} + +- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error { + (void)updater; + if ([error code] == SUNoUpdateError) { + onNewUpdateState(AutoUpdater::State::NoUpdateAvailable); + } + else { + SWIFT_LOG(error) << ns2StdString([error localizedDescription]); + onNewUpdateState(AutoUpdater::State::ErrorCheckingForUpdate); + } +} + +@end |