summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'SwifTools/AutoUpdater')
-rw-r--r--SwifTools/AutoUpdater/AutoUpdater.h1
-rw-r--r--SwifTools/AutoUpdater/SparkleAutoUpdater.h9
-rw-r--r--SwifTools/AutoUpdater/SparkleAutoUpdater.mm90
3 files changed, 95 insertions, 5 deletions
diff --git a/SwifTools/AutoUpdater/AutoUpdater.h b/SwifTools/AutoUpdater/AutoUpdater.h
index a125229..fdd3a91 100644
--- a/SwifTools/AutoUpdater/AutoUpdater.h
+++ b/SwifTools/AutoUpdater/AutoUpdater.h
@@ -1,40 +1,41 @@
/*
* 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:
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;
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/SparkleAutoUpdater.h b/SwifTools/AutoUpdater/SparkleAutoUpdater.h
index 48b75e5..26e08da 100644
--- a/SwifTools/AutoUpdater/SparkleAutoUpdater.h
+++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.h
@@ -1,37 +1,38 @@
/*
* 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 {
/**
* @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();
+ ~SparkleAutoUpdater() override;
- void setAppcastFeed(const std::string& appcastFeed);
- void checkForUpdates();
- State getCurrentState();
+ void setAppcastFeed(const std::string& appcastFeed) override;
+ void checkForUpdates() override;
+ State getCurrentState() override;
+ bool applicationInstallationLocationWritable() override;
private:
void setCurrentState(State updatedState);
private:
class Private;
const std::unique_ptr<Private> d;
};
}
diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm
index 4cf5837..b4a4c05 100644
--- a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm
+++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm
@@ -1,70 +1,158 @@
/*
* Copyright (c) 2016-2017 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;
boost::intrusive_ptr<SparkleAutoUpdaterDelegate> delegate;
State currentState = State::NotCheckedForUpdatesYet;
};
SparkleAutoUpdater::SparkleAutoUpdater(const std::string& appcastFeed) : d(new Private()) {
d->updater = [SUUpdater sharedUpdater];
[d->updater retain];
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()];
[d->updater setAutomaticallyChecksForUpdates: true];
// Automatically check for an update after a day.
[d->updater setUpdateCheckInterval: 86400];
- [d->updater setAutomaticallyDownloadsUpdates: true];
+
+ 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.") << std::endl;
setAppcastFeed(appcastFeed);
}
SparkleAutoUpdater::~SparkleAutoUpdater() {
[d->updater release];
}
void SparkleAutoUpdater::setAppcastFeed(const std::string& appcastFeed) {
NSURL* nsurl = [NSURL URLWithString: std2NSString(appcastFeed)];
[d->updater setFeedURL: nsurl];
}
void SparkleAutoUpdater::checkForUpdates() {
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);
+}
+
}