Add auto update feature (#889)

* Added auto update feature.

* Copied sparkle-readme.md and appcast.example.xml from seadrive-gui.
This commit is contained in:
haidian6672 2017-04-08 11:07:40 +08:00 committed by Shuai Lin
parent 6b1d4cf111
commit b5a275e98d
11 changed files with 527 additions and 2 deletions

View file

@ -37,6 +37,11 @@ OPTION(BUILD_SHIBBOLETH_SUPPORT "Build Shibboleth support" OFF)
option(BUILD_ENABLE_WARNINGS "Enable compiler warnings." ON)
option(BUILD_SPARKLE_SUPPORT "Build Sparkle support" OFF)
IF (BUILD_SPARKLE_SUPPORT)
ADD_DEFINITIONS(-DHAVE_SPARKLE_SUPPORT)
ENDIF()
MESSAGE("Build type: ${CMAKE_BUILD_TYPE}")
## build in PIC mode
@ -79,8 +84,21 @@ IF (WIN32)
IF (${CMAKE_BUILD_TYPE} MATCHES Release)
SET(GUI_TYPE WIN32)
ENDIF()
SET(platform_specific_moc_headers src/ext-handler.h)
SET(platform_specific_sources src/ext-handler.cpp)
SET(platform_specific_moc_headers ${platform_specific_moc_headers} src/ext-handler.h src/ui/about-dialog.h)
SET(platform_specific_sources ${platform_specific_sources} src/ext-handler.cpp src/ui/about-dialog.cpp)
SET(platform_specific_ui_files ${platform_specific_ui_files} ui/about-dialog.ui)
IF (BUILD_SPARKLE_SUPPORT)
IF(NOT EXISTS "${CMAKE_SOURCE_DIR}/WinSparkle.lib")
message(FATAL_ERROR "File ${CMAKE_SOURCE_DIR}/WinSparkle.lib not found in current directory. Please setup winsparkle correctly." )
ENDIF()
SET(SPARKLE_LIBS ${CMAKE_SOURCE_DIR}/WinSparkle.lib)
SET(platform_specific_moc_headers ${platform_specific_moc_headers} src/auto-update-service.h)
SET(platform_specific_sources ${platform_specific_sources} src/auto-update-service.cpp)
IF (NOT (${CMAKE_BUILD_TYPE} MATCHES Release))
ADD_DEFINITIONS(-DWINSPARKLE_DEBUG)
ENDIF()
ENDIF()
ELSEIF (${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "BSD")
INCLUDE_DIRECTORIES(${QT_QTDBUS_INCLUDE_DIR})
LINK_DIRECTORIES(${QT_QTDBUS_LIBRARIES})
@ -702,6 +720,7 @@ ADD_EXECUTABLE(seafile-applet ${GUI_TYPE}
INSTALL(TARGETS seafile-applet DESTINATION bin)
TARGET_LINK_LIBRARIES(seafile-applet
${SPARKLE_LIBS}
${SC_LIBS}
${QT_LIBRARIES}
${OPENSSL_LIBRARIES}

17
appcast.example.xml Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>Seafile client updates</title>
<description>Appcast for test Seafile client updates.</description>
<language>en</language>
<item>
<title>Version 6.0.4</title>
<sparkle:releaseNotesLink>http://localhost/SeafileClientChangeLog.html</sparkle:releaseNotesLink>
<pubDate>Tue, 16 Sep 2016 18:11:12 +0200</pubDate>
<enclosure url="http://localhost/qt.exe"
sparkle:version="6.0.4"
length="0"
type="application/octet-stream"/>
</item>
</channel>
</rss>

39
sparkle-readme.md Normal file
View file

@ -0,0 +1,39 @@
## Setup WinSparkle environment
[WinSparkle](https://github.com/vslavik/winsparkle) 是 Mac 上的 Sparkle 框架在 windows 上的实现,用于软件自动更新.
* 下载 winsparkle 发布包 https://github.com/vslavik/winsparkle/releases/download/v0.5.3/WinSparkle-0.5.3.zip, 并解压
* 把 include 下的文件拷贝到 /usr/local/lib
* 把 Release/winsparkle.dll 拷贝到 /mingw32/bin
* 把 winsparkle.lib 拷贝到 seafile-client 目录下
在编译时需要加上 `BUILD_SPARKLE_SUPPORT` flag:
```sh
cmake -DBUILD_SPARKLE_SUPPORT=ON .
```
## 更新 appcast.xml
winsparkle 根据下载下来的 appcast.xml 中的数据:
- 判断当前是否有更新版本
- 新版本的下载地址
- 新版本的 release notes (展示给用户看)
发布新的版本时需要更新appcast中的哪些字段:
- pubDate 字段
- enclosure 中新版本的下载地址 url 字段
- enclosure 中新版本的版本号 sparkle:version 字段
### 本地开发时如何搭建一个简单的 server 来让 seafile-client 去获取 appcast.xml
- 将 appcast.example.xml 修改一下
- 然后本地启动一个 nginx 服务器
- 设置 `SEAFILE_CLIENT_APPCAST_URI` 环境变量, 然后启动 seafile-applet.
```sh
export SEAFILE_CLIENT_APPCAST_URI=http://localhost:8888/appcast.xml
./seafile-applet.exe
```

View file

@ -0,0 +1,93 @@
#include <QTimer>
#include <winsparkle.h>
#include "api/requests.h"
#include "seafile-applet.h"
#include "utils/utils.h"
#include "auto-update-service.h"
SINGLETON_IMPL(AutoUpdateService)
namespace
{
const char *kSparkleAppcastURI = "https://seafile.com/api/seafile-client/appcast.xml";
const char *kWinSparkleRegistryPath = "SOFTWARE\\Seafile\\Seafile Client\\WinSparkle";
} // namespace
AutoUpdateService::AutoUpdateService(QObject *parent) : QObject(parent)
{
}
void AutoUpdateService::start()
{
// Initialize the updater and possibly show some UI
win_sparkle_init();
}
void AutoUpdateService::stop()
{
win_sparkle_cleanup();
}
void AutoUpdateService::checkUpdate()
{
win_sparkle_check_update_with_ui();
}
void AutoUpdateService::checkAndInstallUpdate() {
win_sparkle_check_update_with_ui_and_install();
}
void AutoUpdateService::checkUpdateWithoutUI() {
win_sparkle_check_update_without_ui();
}
void AutoUpdateService::setRequestParams() {
// Note that @param path is relative to HKCU/HKLM root
// and the root is not part of it. For example:
// @code
// win_sparkle_set_registry_path("Software\\My App\\Updates");
// @endcode
win_sparkle_set_registry_path(kWinSparkleRegistryPath);
win_sparkle_set_appcast_url(getAppcastURI().toUtf8().data());
win_sparkle_set_app_details(
L"Seafile",
L"Seafile Client",
QString(STRINGIZE(SEAFILE_CLIENT_VERSION)).toStdWString().c_str());
}
bool AutoUpdateService::shouldSupportAutoUpdate() const {
// qWarning() << "shouldSupportAutoUpdate =" << (QString(getBrand()) == "Seafile");
return QString(getBrand()) == "Seafile";
}
bool AutoUpdateService::autoUpdateEnabled() const {
// qWarning() << "autoUpdateEnabled =" << win_sparkle_get_automatic_check_for_updates();
return win_sparkle_get_automatic_check_for_updates();
}
void AutoUpdateService::setAutoUpdateEnabled(bool enabled) {
// qWarning() << "setAutoUpdateEnabled:" << enabled;
win_sparkle_set_automatic_check_for_updates(enabled ? 1 : 0);
}
uint AutoUpdateService::updateCheckInterval() const {
return win_sparkle_get_update_check_interval();
}
void AutoUpdateService::setUpdateCheckInterval(uint interval_in_seconds) {
win_sparkle_set_update_check_interval(interval_in_seconds);
}
QString AutoUpdateService::getAppcastURI() {
#if defined(WINSPARKLE_DEBUG)
QString url_from_env = qgetenv("SEAFILE_CLIENT_APPCAST_URI");
return url_from_env.isEmpty() ? kSparkleAppcastURI : url_from_env;
#else
return kSparkleAppcastURI;
#endif
}

37
src/auto-update-service.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef SEAFILE_CLIENT_AUTO_UPDATE_SERVICE_H
#define SEAFILE_CLIENT_AUTO_UPDATE_SERVICE_H
#include <QObject>
#include <QString>
#include "utils/singleton.h"
// Auto update seafile client program. Only used on windows.
class AutoUpdateService : public QObject
{
SINGLETON_DEFINE(AutoUpdateService)
Q_OBJECT
public:
AutoUpdateService(QObject *parent = 0);
bool shouldSupportAutoUpdate() const;
void setRequestParams();
bool autoUpdateEnabled() const;
void setAutoUpdateEnabled(bool enabled);
uint updateCheckInterval() const;
void setUpdateCheckInterval(uint interval_in_seconds);
void start();
void stop();
void checkUpdate();
void checkAndInstallUpdate();
void checkUpdateWithoutUI();
private:
QString getAppcastURI();
};
#endif // SEAFILE_CLIENT_AUTO_UPDATE_SERVICE_H

View file

@ -127,6 +127,11 @@ void handleCommandLineOption(int argc, char *argv[])
case 'f':
OpenLocalHelper::instance()->handleOpenLocalFromCommandLine(optarg);
break;
#if defined(HAVE_SPARKLE_SUPPORT) && defined(WINSPARKLE_DEBUG)
case 'U':
g_setenv ("SEAFILE_CLIENT_APPCAST_URI", optarg, 1);
break;
#endif
default:
exit(1);
}

View file

@ -44,6 +44,10 @@
#if defined(Q_OS_WIN32)
#include "ext-handler.h"
#include "utils/registry.h"
#ifdef HAVE_SPARKLE_SUPPORT
#include "auto-update-service.h"
#endif
#elif defined(HAVE_FINDER_SYNC_SUPPORT)
#include "finder-sync/finder-sync-listener.h"
#endif
@ -262,6 +266,11 @@ SeafileApplet::~SeafileApplet()
delete main_win_;
#if defined(Q_OS_WIN32)
SeafileExtensionHandler::instance()->stop();
#ifdef HAVE_SPARKLE_SUPPORT
AutoUpdateService::instance()->stop();
#endif
#endif
}
@ -404,6 +413,14 @@ void SeafileApplet::onDaemonStarted()
//
#if defined(Q_OS_WIN32)
SeafileExtensionHandler::instance()->start();
#ifdef HAVE_SPARKLE_SUPPORT
if (AutoUpdateService::instance()->shouldSupportAutoUpdate()) {
AutoUpdateService::instance()->setRequestParams();
AutoUpdateService::instance()->start();
}
#endif // HAVE_SPARKLE_SUPPORT
#elif defined(HAVE_FINDER_SYNC_SUPPORT)
finderSyncListenerStart();
#endif

52
src/ui/about-dialog.cpp Normal file
View file

@ -0,0 +1,52 @@
#include <QtWidgets>
#include "seafile-applet.h"
#include "utils/utils.h"
#include "about-dialog.h"
#ifdef HAVE_SPARKLE_SUPPORT
#include "auto-update-service.h"
namespace {
} // namespace
#endif
AboutDialog::AboutDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
setWindowTitle(tr("About %1").arg(getBrand()));
setWindowIcon(QIcon(":/images/seafile.png"));
setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) |
Qt::WindowStaysOnTopHint);
version_text_ = tr("<h4>%1 Client %2</h4>")
.arg(getBrand())
.arg(STRINGIZE(SEAFILE_CLIENT_VERSION))
#ifdef SEAFILE_CLIENT_REVISION
.append(tr("<h5> REV %1 </h5>"))
.arg(STRINGIZE(SEAFILE_CLIENT_REVISION))
#endif
;
mVersionText->setText(version_text_);
connect(mOKBtn, SIGNAL(clicked()), this, SLOT(close()));
#ifdef HAVE_SPARKLE_SUPPORT
mCheckUpdateBtn->setVisible(true);
connect(mCheckUpdateBtn, SIGNAL(clicked()), this, SLOT(checkUpdate()));
#else
mCheckUpdateBtn->setVisible(false);
#endif
}
#ifdef HAVE_SPARKLE_SUPPORT
void AboutDialog::checkUpdate()
{
AutoUpdateService::instance()->setRequestParams();
AutoUpdateService::instance()->checkUpdate();
close();
}
#endif

27
src/ui/about-dialog.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef SEAFILE_CLIENT_ABOUT_DIALOG_H
#define SEAFILE_CLIENT_ABOUT_DIALOG_H
#include <QDialog>
#include "ui_about-dialog.h"
class AutoUpdateService;
class AboutDialog : public QDialog,
public Ui::AboutDialog
{
Q_OBJECT
public:
AboutDialog(QWidget *parent=0);
#ifdef HAVE_SPARKLE_SUPPORT
private slots:
void checkUpdate();
#endif
private:
Q_DISABLE_COPY(AboutDialog)
QString version_text_;
};
#endif // SEAFILE_CLIENT_ABOUT_DIALOG_H

View file

@ -48,6 +48,10 @@ extern void qt_mac_set_dock_menu(QMenu *menu);
#include <QDBusPendingCall>
#endif
#if defined(Q_OS_WIN32)
#include "src/ui/about-dialog.h"
#endif
namespace {
const int kRefreshInterval = 1000;
@ -488,6 +492,14 @@ void SeafileTrayIcon::showMainWindow()
void SeafileTrayIcon::about()
{
#if defined(Q_OS_WIN32)
AboutDialog *about_dialog = new AboutDialog();
about_dialog->show();
about_dialog->raise();
about_dialog->activateWindow();
return;
#endif
QMessageBox::about(seafApplet->mainWindow(), tr("About %1").arg(getBrand()),
tr("<h2>%1 Client %2</h2>").arg(getBrand()).arg(
STRINGIZE(SEAFILE_CLIENT_VERSION))

207
ui/about-dialog.ui Normal file
View file

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>296</width>
<height>137</height>
</rect>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>6</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="mVersionText">
<property name="font">
<font>
<family>Times New Roman</family>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string notr="true">Current Version</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>6</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>7</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QPushButton" name="mCheckUpdateBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>92</width>
<height>23</height>
</size>
</property>
<property name="text">
<string>Check For Updates</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mOKBtn">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>6</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="9"/>
<resources/>
<connections/>
<designerdata>
<property name="gridDeltaX">
<number>10</number>
</property>
<property name="gridDeltaY">
<number>10</number>
</property>
<property name="gridSnapX">
<bool>true</bool>
</property>
<property name="gridSnapY">
<bool>true</bool>
</property>
<property name="gridVisible">
<bool>false</bool>
</property>
</designerdata>
</ui>