mirror of
https://github.com/etlegacy/Update-Installer.git
synced 2025-04-05 22:01:21 +00:00
Compare commits
83 commits
Author | SHA1 | Date | |
---|---|---|---|
|
bb76b067ec | ||
|
1c4e9d87a7 | ||
|
40c35f93cf | ||
|
89bb4b2edc | ||
|
a60da9122b | ||
|
9b72b1ad18 | ||
|
a779ecbbda | ||
|
8e4df618c3 | ||
|
c24408d255 | ||
|
55f8918641 | ||
|
3904349a06 | ||
|
0ef971a251 | ||
|
ac93a8b06f | ||
|
8a68ba2844 | ||
|
abe12806e5 | ||
|
e2441e3e67 | ||
|
4459e0d6c0 | ||
|
81f80d26d2 | ||
|
f6ded2035f | ||
|
bb9c0bbc80 | ||
|
3cce9d3058 | ||
|
f201a6cd7d | ||
|
21dd167d8b | ||
|
1b0d933ad7 | ||
|
3223bcff7a | ||
|
a8755f9464 | ||
|
373caf74a7 | ||
|
5d2ce7e8c3 | ||
|
ddce2d6e73 | ||
|
7335ee3eac | ||
|
465788b400 | ||
|
4df97c84e1 | ||
|
6a56b5cddc | ||
|
e74435e871 | ||
|
83b2697b52 | ||
|
d25cfb8583 | ||
|
ec3d09000b | ||
|
0472a4e991 | ||
|
b168ed69d7 | ||
|
2658f1235d | ||
|
484f8ae37b | ||
|
1ebf62e1fa | ||
|
84288ddc6c | ||
|
cbda873a70 | ||
|
c09e5d2290 | ||
|
b1bf64a671 | ||
|
e55a58e4be | ||
|
b6a13d3faa | ||
|
dab236d8de | ||
|
401785786a | ||
|
5cd4484f8b | ||
|
39b8ada727 | ||
|
d44b0638bf | ||
|
7864ad1829 | ||
|
4e2e5e9c68 | ||
|
b60a7a7ba2 | ||
|
16baf00ee6 | ||
|
80fdc5b048 | ||
|
92ef486dc6 | ||
|
2a59c6f888 | ||
|
0d1f285abb | ||
|
93365a2997 | ||
|
de8531c583 | ||
|
c54ecee1a9 | ||
|
9823b70c5a | ||
|
932cddfb77 | ||
|
666c36a4bd | ||
|
66a1f6be9e | ||
|
8a5bed77df | ||
|
66bafb317f | ||
|
df89da7ebd | ||
|
99815159b4 | ||
|
02fa638261 | ||
|
f49c147c4f | ||
|
78c14868c6 | ||
|
54afb7ca00 | ||
|
addc3b253d | ||
|
60357cada9 | ||
|
c9543e59f6 | ||
|
3319c4ad3e | ||
|
61e8c92550 | ||
|
1898c7de69 | ||
|
773edb5d53 |
49 changed files with 960 additions and 497 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
project/
|
|
@ -1,27 +1,44 @@
|
|||
project(updater)
|
||||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
|
||||
enable_testing()
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
# If the SIGN_UPDATER option is enabled, the updater.exe
|
||||
# binary is digitally signed on Windows after it has been built.
|
||||
# A script called sign-updater.bat must exist in the PATH or
|
||||
# the directory where updater.exe is built. This will be run
|
||||
# with the path of the binary to sign it.
|
||||
option(SIGN_UPDATER OFF)
|
||||
#
|
||||
# The script used to sign the binary is specified by BINARY_SIGNING_TOOL.
|
||||
# This tool must take a single argument which is the filename of the
|
||||
# binary to sign.
|
||||
option(SIGN_UPDATER "Enable signing of the updater binary" OFF)
|
||||
set(BINARY_SIGNING_TOOL sign-updater.bat CACHE PATH "Path to the tool used to sign the updater")
|
||||
|
||||
include_directories(external)
|
||||
include_directories(external/TinyThread/source)
|
||||
|
||||
if (WIN32)
|
||||
include_directories(external/zlib/)
|
||||
include_directories(external/bzip2)
|
||||
include_directories(external/win32cpp/include)
|
||||
include_directories(external/zlib/)
|
||||
include_directories(external/bzip2)
|
||||
include_directories(external/win32cpp/include)
|
||||
|
||||
# Link the updater binary statically with the Visual C++ runtime
|
||||
# so that the executable can function standalone
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG")
|
||||
# - Link the updater binary statically with the Visual C++ runtime
|
||||
# so that the executable can function standalone.
|
||||
# - Enable PDB generation for release builds
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/MT")
|
||||
set(CMAKE_C_FLAGS_DEBUG "/MT")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "/MT /Zi /O2 /Ob2 /D NDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELEASE "/MT /Zi /O2 /Ob2 /D NDEBUG")
|
||||
|
||||
remove_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS) # Do not show CRT warnings
|
||||
endif()
|
||||
else()
|
||||
# optimize for reduced code size
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-Os")
|
||||
set(CMAKE_C_FLAGS_RELEASE "-Os")
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
|
@ -30,14 +47,12 @@ if (APPLE)
|
|||
# of the updater binary
|
||||
set(CMAKE_OSX_ARCHITECTURES i386;x86_64)
|
||||
|
||||
# Build the updater so that it works on OS X 10.5 and above.
|
||||
# If you are building with XCode 4 or newer, you will need to up
|
||||
# this target - depending on the versions of the Mac OS X SDK that
|
||||
# are available to build with in /Developer/SDKs/
|
||||
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.5)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
set(CMAKE_OSX_SYSROOT "/Developer/SDKs/MacOSX${CMAKE_OSX_DEPLOYMENT_TARGET}.sdk")
|
||||
# Build the updater so that it works on OS X 10.6 and above.
|
||||
if (NOT (DEFINED MIN_OSX_DEPLOYMENT_VERSION))
|
||||
set(MIN_OSX_DEPLOYMENT_VERSION 10.6)
|
||||
endif()
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=${MIN_OSX_DEPLOYMENT_VERSION}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${MIN_OSX_DEPLOYMENT_VERSION}")
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
|
|
|
@ -17,44 +17,35 @@ the location of the compressed packages and the path to the update script.
|
|||
Once the updater has been started, it:
|
||||
|
||||
1. Waits for the application to exit
|
||||
|
||||
2. Acquires the necessary priviledges to install the updates, prompting
|
||||
the user if necessary.
|
||||
|
||||
3. Installs the updates, displaying progress to the user in a small dialog
|
||||
|
||||
4. Performs cleanup and any additional actions required as part of the update
|
||||
|
||||
5. Starts the new version of the main application.
|
||||
|
||||
In the event of a failure during the update, the installation is rolled back
|
||||
to its previous state and a message is presented to the user.
|
||||
|
||||
Building the Updater
|
||||
====================
|
||||
## Building the Updater
|
||||
|
||||
Create a new directory for the build and from that directory run:
|
||||
|
||||
cmake <path to source directory>
|
||||
make
|
||||
cmake <path to source directory>
|
||||
make
|
||||
|
||||
The updater binary will be built in the src/ directory.
|
||||
|
||||
You should also run the tests in src/tests to verify that the updater is
|
||||
functioning correctly.
|
||||
|
||||
Preparing an Update
|
||||
===================
|
||||
## Preparing an Update
|
||||
|
||||
1. Create a directory containing your application's files,
|
||||
laid out in the same way and with the same permissions as they would be when installed.
|
||||
|
||||
2. Create a config file specifying how the application's files should be
|
||||
partitioned into packages - see tools/config-template.json
|
||||
|
||||
3. Use the tools/create-packages.rb script to create a file_list.xml file
|
||||
and a set of package files required for updates.
|
||||
|
||||
4. Upload the file_list.xml file and packages to a server
|
||||
|
||||
After step 4 is done, you need to notify existing installs that an update
|
||||
|
@ -64,45 +55,40 @@ Preparing an Update
|
|||
|
||||
See doc/update-hosting for more details on hosting and delivering the updates.
|
||||
|
||||
Invoking the Updater
|
||||
====================
|
||||
## Invoking the Updater
|
||||
|
||||
Once the application has downloaded an update, it needs to invoke it. The syntax is:
|
||||
|
||||
updater --install-dir <install-dir> --package-dir <package-dir> --script <script file>
|
||||
updater --install-dir <install-dir> --package-dir <package-dir> --script <script file>
|
||||
|
||||
Where <install-dir> is the directory which the application is installed into,
|
||||
<package-dir> is the directory containing the packages required for the update
|
||||
and <script> is the file_list.xml file describing the update.
|
||||
Where `<install-dir>` is the directory which the application is installed into,
|
||||
`<package-dir>` is the directory containing the packages required for the update
|
||||
and `<script>` is the `file_list.xml` file describing the update.
|
||||
|
||||
Once the updater has run, it will launch the file specified in the file_list.xml file
|
||||
Once the updater has run, it will launch the file specified in the `file_list.xml` file
|
||||
as being the main application binary.
|
||||
|
||||
See the updater test in src/tests/test-update.rb for an example
|
||||
See the updater test in `src/tests/test-update.rb` for an example
|
||||
of how to invoke the updater.
|
||||
|
||||
You should design the process used to download and launch the updater so that new
|
||||
versions of the updater itself can be delivered as part of the update if necessary.
|
||||
|
||||
Customizing the Updater
|
||||
=======================
|
||||
## Customizing the Updater
|
||||
|
||||
To customize the application name, organization and messages displayed by the updater:
|
||||
|
||||
1. Edit the AppInfo class (in AppInfo.h, AppInfo.cpp) to set the name
|
||||
of the application and associated organization.
|
||||
|
||||
2. Replace the icons in src/resources
|
||||
|
||||
3. Change the product name and organization in src/resources/updater.rc
|
||||
|
||||
4. If you are building the updater on Windows and have a suitable Authenticode
|
||||
certificate, use it to sign the Windows binary. This will make the application
|
||||
show a less scary UAC prompt if administrator permissions are required
|
||||
to complete the installation.
|
||||
|
||||
Updater Dependencies
|
||||
====================
|
||||
## Updater Dependencies
|
||||
|
||||
The external dependencies of the updater binary are:
|
||||
|
||||
* The C/C++ runtime libraries (Linux, Mac),
|
||||
|
@ -110,8 +96,7 @@ Updater Dependencies
|
|||
* zlib (Linux, Mac)
|
||||
* native UI library (Win32 API on Windows, Cocoa on Mac, GTK on Linux if available)
|
||||
|
||||
Full and Delta Updates
|
||||
======================
|
||||
## Full and Delta Updates
|
||||
|
||||
The simplest auto-update implementation is for existing installs
|
||||
to download a complete copy of the new version and install it. This is
|
||||
|
@ -131,23 +116,23 @@ Full and Delta Updates
|
|||
|
||||
There are several ways in which this can be done:
|
||||
|
||||
Pre-computed Delta Updates
|
||||
- For each release, create a full update plus delta updates from the
|
||||
previous N releases. Users of recent releases will receive a small
|
||||
delta update. Users of older releases will receive the full update.
|
||||
* Pre-computed Delta Updates
|
||||
For each release, create a full update plus delta updates from the
|
||||
previous N releases. Users of recent releases will receive a small
|
||||
delta update. Users of older releases will receive the full update.
|
||||
|
||||
Server-computed Delta Updates
|
||||
- The server receives a request for an update from client version X and in response,
|
||||
computes an update from version X to the current version Y, possibly
|
||||
caching that information for future use. The client then receives the
|
||||
delta file_list.xml file and downloads only the listed packages.
|
||||
* Server-computed Delta Updates
|
||||
The server receives a request for an update from client version X and in response,
|
||||
computes an update from version X to the current version Y, possibly
|
||||
caching that information for future use. The client then receives the
|
||||
delta file_list.xml file and downloads only the listed packages.
|
||||
|
||||
Applications such as Chrome and Firefox use a mixture of the above methods.
|
||||
Applications such as Chrome and Firefox use a mixture of the above methods.
|
||||
|
||||
Client-computed Delta Updates
|
||||
- The client downloads the file_list.xml file for the latest version and
|
||||
computes a delta update file locally. It then downloads only the required
|
||||
packages and invokes the updater, which installs only the changed or updated
|
||||
files from those packages.
|
||||
* Client-computed Delta Updates
|
||||
The client downloads the file_list.xml file for the latest version and
|
||||
computes a delta update file locally. It then downloads only the required
|
||||
packages and invokes the updater, which installs only the changed or updated
|
||||
files from those packages.
|
||||
|
||||
This is similar to Linux package management systems.
|
||||
This is similar to Linux package management systems.
|
72
TODO
72
TODO
|
@ -1,72 +0,0 @@
|
|||
General Updater Tasks:
|
||||
|
||||
* Elevation for Linux [done]
|
||||
* Set file permissions for Unix [done]
|
||||
* Elevation for Mac [done]
|
||||
* Basic functionality for Mac [done]
|
||||
* Unit test working under Mac [done]
|
||||
* Set file permissions for Windows [skipped for now]
|
||||
* Basic functionality for Windows [done]
|
||||
* Elevation for Windows [done]
|
||||
* Win32 UI for Windows [done]
|
||||
* Cocoa UI for Mac [done]
|
||||
* Gtk or Qt UI for Linux [done - GTK UI]
|
||||
* Unit test working under Windows [done]
|
||||
* Start new application once installation is finished [done]
|
||||
* Use message box for errors [done]
|
||||
* Newly installed binary needs to be launched un-elevated [done]
|
||||
* See if it is possible to overwrite application files that are in use on Windows [done - it is possible]
|
||||
* Fix package dir cleanup failing on Win32 due to executable being in use [done]
|
||||
|
||||
* Test installing update if Microsoft Word with Mendeley plugin is active [done]
|
||||
* Write log file entries to an actual log file [done]
|
||||
* Test updater on an old Windows system without Visual Studio installed and statically
|
||||
link C++ runtime libraries if necessary
|
||||
|
||||
Mendeley-specific Updater Tasks:
|
||||
* Use the Mendeley icon for the window on Windows [done]
|
||||
* Use the Mendeley icon for the binary on Windows [done]
|
||||
* Use the Mendeley icon for the window on Mac [done]
|
||||
* Use the Mendeley icon for the window on Linux
|
||||
|
||||
* Advise the user to download Mendeley Desktop afresh from http://www.mendeley.com/download-mendeley-desktop
|
||||
in the event of an updater problem [done]
|
||||
* Exclude Uninstall.exe from updates on Windows - see comments in utilities/autoupdate-setup/main.cpp in
|
||||
the desktop source tree.
|
||||
|
||||
* Updater binary needs to be signed under Windows [done]
|
||||
|
||||
* Improve elevation dialog on Mac - use correct app icon and description for app
|
||||
|
||||
Mendeley Desktop <= 1.0 auto-update system compatibility:
|
||||
|
||||
* Support for MD <= 1.0 updater command-line syntax [done]
|
||||
* Backwards compatible structure for XML file [done]
|
||||
|
||||
Auto-update preparation tools:
|
||||
|
||||
* Tool to create .zip packages for a release and
|
||||
upload them to S3 [done]
|
||||
* Tool to generate backwards-compatible structure for XML file [done]
|
||||
* Tool to generate new structure for XML file [done]
|
||||
|
||||
Nice To Have
|
||||
============
|
||||
|
||||
Update size:
|
||||
* Support for applying binary patches (eg. with bspatch/bsdiff)
|
||||
* Try using bzip2 compression instead of standard zip compression
|
||||
(requires recent version of zip/unzip tools) [done]
|
||||
|
||||
Telemetry:
|
||||
* Call a project-specific URL to report successful/failed update installation
|
||||
and starting of new app after update
|
||||
|
||||
Source:
|
||||
* Ensure no Mendeley branding in standalone project and publish code [done - left Mendeley icons in
|
||||
code and make a note that anyone re-using the code will need to replace them]
|
||||
|
||||
Reliability:
|
||||
* Create a lock to prevent Mendeley being started whilst updates are
|
||||
in progress and to prevent multiple updates being run at once.
|
||||
* Consider using file system transactions on Windows to make update installation atomic
|
BIN
external/bzip2/libbz2.a
vendored
Normal file
BIN
external/bzip2/libbz2.a
vendored
Normal file
Binary file not shown.
18
external/minizip/CMakeLists.txt
vendored
18
external/minizip/CMakeLists.txt
vendored
|
@ -18,7 +18,7 @@ set (HEADERS
|
|||
zip.h
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
if (APPLE OR ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||
# Mac OS X does not have fopen64()
|
||||
# and several related functions as the standard fopen()
|
||||
# calls are 64bit
|
||||
|
@ -33,7 +33,21 @@ add_library(minizip
|
|||
)
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(minizip z bz2)
|
||||
# on Mac, link to libbz2 dynamically, on Linux
|
||||
# we link statically to libbz2 so that an updater binary
|
||||
# build on Debian (where the packaged libbz2 has a SONAME of "libbz2.so.1.0"
|
||||
# works on Fedora/openSUSE (where no libbz2.so.1.0 symlink exists)
|
||||
#
|
||||
# see http://stackoverflow.com/questions/1835489/linking-an-application-to-libbz2-so-1-rather-than-libbz2-so-1-0
|
||||
#
|
||||
find_package(BZip2)
|
||||
|
||||
if(APPLE)
|
||||
set(BZ2_LIB_NAME bz2)
|
||||
target_link_libraries(minizip z ${BZ2_LIB_NAME})
|
||||
else()
|
||||
target_link_libraries(minizip z "${CMAKE_CURRENT_SOURCE_DIR}/../bzip2/libbz2.a")
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(minizip
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../zlib/prebuilt/zlib_static.lib"
|
||||
|
|
|
@ -17,7 +17,7 @@ std::string AppInfo::updateErrorMessage(const std::string& details)
|
|||
std::string result = "There was a problem installing the update:\n\n";
|
||||
result += details;
|
||||
result += "\n\nYou can try downloading and installing the latest version of "
|
||||
"Mendeley Desktop from http://www.mendeley.com/download-mendeley-desktop";
|
||||
"ET:Legacy from http://www.etlegacy.com/";
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,16 +24,16 @@ class AppInfo
|
|||
|
||||
inline std::string AppInfo::name()
|
||||
{
|
||||
return "Mendeley Updater";
|
||||
return "Legacy Updater";
|
||||
}
|
||||
|
||||
inline std::string AppInfo::appName()
|
||||
{
|
||||
return "Mendeley Desktop";
|
||||
return "ET:Legacy";
|
||||
}
|
||||
|
||||
inline std::string AppInfo::organizationName()
|
||||
{
|
||||
return "Mendeley Ltd.";
|
||||
return "Legacy Team.";
|
||||
}
|
||||
|
||||
|
|
|
@ -8,18 +8,15 @@ if (UNIX)
|
|||
add_definitions(-Wall -Werror -Wconversion)
|
||||
endif()
|
||||
|
||||
include(UsePkgConfig)
|
||||
pkgconfig(gtk+-2.0 GTK2_INCLUDE_DIR GTK2_LINK_DIR GTK2_LINK_FLAGS GTK2_CFLAGS)
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
include_directories(${GTK2_INCLUDE_DIR})
|
||||
add_definitions(${GTK2_CFLAGS})
|
||||
find_package(GTK2 REQUIRED gtk)
|
||||
include_directories(${GTK2_INCLUDE_DIRS})
|
||||
add_library(updatergtk SHARED UpdateDialogGtk.cpp UpdateDialogGtk.h)
|
||||
target_link_libraries(updatergtk ${GTK2_LINK_FLAGS})
|
||||
target_link_libraries(updatergtk ${GTK2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
add_definitions(-DTIXML_USE_STL)
|
||||
|
@ -31,6 +28,7 @@ set (SOURCES
|
|||
Log.cpp
|
||||
ProcessUtils.cpp
|
||||
StandardDirs.cpp
|
||||
UpdateDialog.cpp
|
||||
UpdateInstaller.cpp
|
||||
UpdateScript.cpp
|
||||
UpdaterOptions.cpp
|
||||
|
@ -84,8 +82,8 @@ if (LINUX)
|
|||
generate_cpp_resource_file(resource_updatergtk ${CMAKE_CURRENT_BINARY_DIR} ${GTK_UPDATER_LIB} ${GTK_BIN_CPP_FILE})
|
||||
add_dependencies(resource_updatergtk updatergtk)
|
||||
|
||||
set(SOURCES ${SOURCES} UpdateDialogGtkWrapper.cpp ${GTK_BIN_CPP_FILE})
|
||||
set(HEADERS ${HEADERS} UpdateDialogGtkWrapper.h)
|
||||
set(SOURCES ${SOURCES} UpdateDialogGtkFactory.cpp ${GTK_BIN_CPP_FILE})
|
||||
set(HEADERS ${HEADERS} UpdateDialogGtkFactory.h)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
|
@ -107,8 +105,21 @@ target_link_libraries(updatershared
|
|||
tinythread
|
||||
)
|
||||
|
||||
if(LINUX)
|
||||
add_dependencies(updatershared resource_updatergtk)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
find_library(SECURITY_LIBRARY Security)
|
||||
target_link_libraries(updatershared ${SECURITY_LIBRARY} ${COCOA_LIBRARY})
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(updatershared pthread dl)
|
||||
target_link_libraries(updatershared pthread)
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
target_link_libraries(updatershared dl)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
|
@ -117,20 +128,15 @@ endif()
|
|||
|
||||
add_executable(updater ${EXE_FLAGS} main.cpp)
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(
|
||||
updater
|
||||
PROPERTIES LINK_FLAGS "-framework Security -framework Cocoa"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(updater
|
||||
updatershared
|
||||
)
|
||||
|
||||
if (SIGN_UPDATER)
|
||||
add_custom_command(TARGET updater POST_BUILD COMMAND sign-updater.bat updater.exe)
|
||||
add_custom_command(TARGET updater POST_BUILD COMMAND ${BINARY_SIGNING_TOOL} $<TARGET_FILE:updater>)
|
||||
endif()
|
||||
|
||||
install(TARGETS updater RUNTIME DESTINATION bin)
|
||||
add_executable(zip-tool zip-tool.cpp)
|
||||
target_link_libraries(zip-tool updatershared)
|
||||
|
||||
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
#include "Platform.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "minizip/zip.h"
|
||||
#include "minizip/unzip.h"
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
|
@ -79,7 +81,7 @@ bool FileUtils::fileExists(const char* path) throw (IOException)
|
|||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
struct stat fileInfo;
|
||||
if (stat(path,&fileInfo) != 0)
|
||||
if (lstat(path,&fileInfo) != 0)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
|
@ -119,7 +121,7 @@ int FileUtils::fileMode(const char* path) throw (IOException)
|
|||
void FileUtils::chmod(const char* path, int mode) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
if (::chmod(path,mode) != 0)
|
||||
if (::chmod(path,static_cast<mode_t>(mode)) != 0)
|
||||
{
|
||||
throw IOException("Failed to set permissions on " + std::string(path) + " to " + intToStr(mode));
|
||||
}
|
||||
|
@ -144,6 +146,37 @@ void FileUtils::moveFile(const char* src, const char* dest) throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileUtils::addToZip(const char* archivePath, const char* path, const char* content, int length) throw (IOException)
|
||||
{
|
||||
int result = ZIP_OK;
|
||||
|
||||
int appendMode = fileExists(archivePath) ? APPEND_STATUS_ADDINZIP : APPEND_STATUS_CREATE;
|
||||
|
||||
zipFile archive = zipOpen(archivePath, appendMode);
|
||||
result = zipOpenNewFileInZip(archive, path, 0 /* file attributes */, 0 /* extra field */, 0 /* extra field size */,
|
||||
0/* global extra field */, 0 /* global extra field size */, 0 /* comment */, Z_DEFLATED /* method */,
|
||||
Z_DEFAULT_COMPRESSION /* level */);
|
||||
if (result != ZIP_OK)
|
||||
{
|
||||
throw IOException("Unable to add new file to zip archive");
|
||||
}
|
||||
result = zipWriteInFileInZip(archive, content, static_cast<unsigned int>(length));
|
||||
if (result != ZIP_OK)
|
||||
{
|
||||
throw IOException("Unable to write file data to zip archive");
|
||||
}
|
||||
result = zipCloseFileInZip(archive);
|
||||
if (result != ZIP_OK)
|
||||
{
|
||||
throw IOException("Unable to close file in zip archive");
|
||||
}
|
||||
result = zipClose(archive, 0 /* global comment */);
|
||||
if (result != ZIP_OK)
|
||||
{
|
||||
throw IOException("Unable to close zip archive");
|
||||
}
|
||||
}
|
||||
|
||||
void FileUtils::extractFromZip(const char* zipFilePath, const char* src, const char* dest) throw (IOException)
|
||||
{
|
||||
unzFile zipFile = unzOpen(zipFilePath);
|
||||
|
@ -298,7 +331,16 @@ std::string FileUtils::fileName(const char* path)
|
|||
#else
|
||||
char baseName[MAX_PATH];
|
||||
char extension[MAX_PATH];
|
||||
_splitpath(path, 0 /* drive */, 0 /* dir */, baseName, extension);
|
||||
_splitpath_s(path,
|
||||
0, /* drive */
|
||||
0, /* drive length */
|
||||
0, /* dir */
|
||||
0, /* dir length */
|
||||
baseName,
|
||||
MAX_PATH, /* baseName length */
|
||||
extension,
|
||||
MAX_PATH /* extension length */
|
||||
);
|
||||
return std::string(baseName) + std::string(extension);
|
||||
#endif
|
||||
}
|
||||
|
@ -311,9 +353,28 @@ std::string FileUtils::dirname(const char* path)
|
|||
free(pathCopy);
|
||||
return dirname;
|
||||
#else
|
||||
char drive[3];
|
||||
char dir[MAX_PATH];
|
||||
_splitpath(path, 0 /* drive */, dir, 0 /* filename */, 0/* extension */);
|
||||
return std::string(dir);
|
||||
|
||||
_splitpath_s(path,
|
||||
drive, /* drive */
|
||||
3, /* drive length */
|
||||
dir,
|
||||
MAX_PATH, /* dir length */
|
||||
0, /* filename */
|
||||
0, /* filename length */
|
||||
0, /* extension */
|
||||
0 /* extension length */
|
||||
);
|
||||
|
||||
std::string result;
|
||||
if (drive[0])
|
||||
{
|
||||
result += std::string(drive);
|
||||
}
|
||||
result += dir;
|
||||
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -404,23 +465,29 @@ std::string FileUtils::canonicalPath(const char* path)
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string FileUtils::toWindowsPathSeparators(const std::string& str)
|
||||
{
|
||||
std::string result = str;
|
||||
std::replace(result.begin(),result.end(),'/','\\');
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileUtils::toUnixPathSeparators(const std::string& str)
|
||||
{
|
||||
std::string result = str;
|
||||
for (size_t i=0; i < result.size(); i++)
|
||||
{
|
||||
if (result[i] == '\\')
|
||||
{
|
||||
result[i] = '/';
|
||||
}
|
||||
}
|
||||
std::replace(result.begin(),result.end(),'\\','/');
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileUtils::tempPath()
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
return "/tmp";
|
||||
std::string tmpDir(notNullString(getenv("TMPDIR")));
|
||||
if (tmpDir.empty())
|
||||
{
|
||||
tmpDir = "/tmp";
|
||||
}
|
||||
return tmpDir;
|
||||
#else
|
||||
char buffer[MAX_PATH+1];
|
||||
GetTempPath(MAX_PATH+1,buffer);
|
||||
|
@ -431,7 +498,7 @@ std::string FileUtils::tempPath()
|
|||
bool startsWithDriveLetter(const char* path)
|
||||
{
|
||||
return strlen(path) >= 2 &&
|
||||
(path[0] >= 'A' && path[0] <= 'Z') &&
|
||||
(isalpha(path[0])) &&
|
||||
path[1] == ':';
|
||||
}
|
||||
|
||||
|
@ -469,6 +536,17 @@ void FileUtils::writeFile(const char* path, const char* data, int length) throw
|
|||
stream.write(data,length);
|
||||
}
|
||||
|
||||
std::string FileUtils::readFile(const char* path) throw (IOException)
|
||||
{
|
||||
std::ifstream inputFile(path, std::ios::in | std::ios::binary);
|
||||
std::string content;
|
||||
inputFile.seekg(0, std::ios::end);
|
||||
content.resize(static_cast<unsigned int>(inputFile.tellg()));
|
||||
inputFile.seekg(0, std::ios::beg);
|
||||
inputFile.read(&content[0], static_cast<int>(content.size()));
|
||||
return content;
|
||||
}
|
||||
|
||||
void FileUtils::copyFile(const char* src, const char* dest) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
|
@ -551,3 +629,41 @@ std::string FileUtils::getcwd() throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
bool FileUtils::removeEmptyDirs(const char* path)
|
||||
{
|
||||
DirIterator iter(path);
|
||||
int fileCount = 0;
|
||||
while (iter.next())
|
||||
{
|
||||
if (iter.fileName() == "." || iter.fileName() == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!iter.isDir() || !removeEmptyDirs(iter.filePath().c_str()))
|
||||
{
|
||||
// entry is either not a directory or is a
|
||||
// directory hierarchy which contains one or more non-directories
|
||||
// once all empty dirs have been recursively removed
|
||||
++fileCount;
|
||||
}
|
||||
}
|
||||
if (fileCount == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileUtils::rmdir(path);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
LOG(Error,"Unable to remove empty directory " + std::string(ex.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
#include "Platform.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
|
||||
/** A set of functions for performing common operations
|
||||
* on files, throwing exceptions if an operation fails.
|
||||
*
|
||||
|
@ -65,8 +67,17 @@ class FileUtils
|
|||
* Unix mode_t values.
|
||||
*/
|
||||
static void chmod(const char* path, int permissions) throw (IOException);
|
||||
|
||||
/** Returns true if the file at @p path exists. If @p path is a symlink,
|
||||
* returns true if the symlink itself exists, not the target.
|
||||
*/
|
||||
static bool fileExists(const char* path) throw (IOException);
|
||||
|
||||
/** Returns the Unix mode flags of @p path. If @p path is a symlink,
|
||||
* returns the mode flags of the target.
|
||||
*/
|
||||
static int fileMode(const char* path) throw (IOException);
|
||||
|
||||
static void moveFile(const char* src, const char* dest) throw (IOException);
|
||||
static void mkdir(const char* dir) throw (IOException);
|
||||
static void rmdir(const char* dir) throw (IOException);
|
||||
|
@ -82,7 +93,9 @@ class FileUtils
|
|||
/** Returns the file name part of a file path, including the extension. */
|
||||
static std::string fileName(const char* path);
|
||||
|
||||
/** Returns the directory part of a file path. */
|
||||
/** Returns the directory part of a file path.
|
||||
* On Windows this includes the drive letter, if present in @p path.
|
||||
*/
|
||||
static std::string dirname(const char* path);
|
||||
|
||||
/** Remove a directory and all of its contents. */
|
||||
|
@ -96,6 +109,9 @@ class FileUtils
|
|||
/** Returns the path to a directory for storing temporary files. */
|
||||
static std::string tempPath();
|
||||
|
||||
/** Add a file to a zip archive, creating it if it does not already exist. */
|
||||
static void addToZip(const char* archivePath, const char* path, const char* content, int length) throw (IOException);
|
||||
|
||||
/** Extract the file @p src from the zip archive @p zipFile and
|
||||
* write it to @p dest.
|
||||
*/
|
||||
|
@ -106,6 +122,8 @@ class FileUtils
|
|||
*/
|
||||
static std::string toUnixPathSeparators(const std::string& str);
|
||||
|
||||
static std::string toWindowsPathSeparators(const std::string& str);
|
||||
|
||||
/** Returns true if the provided path is relative.
|
||||
* Or false if absolute.
|
||||
*/
|
||||
|
@ -120,10 +138,19 @@ class FileUtils
|
|||
|
||||
static void writeFile(const char* path, const char* data, int length) throw (IOException);
|
||||
|
||||
static std::string readFile(const char* path) throw (IOException);
|
||||
|
||||
/** Changes the current working directory to @p path */
|
||||
static void chdir(const char* path) throw (IOException);
|
||||
|
||||
/** Returns the current working directory of the application. */
|
||||
static std::string getcwd() throw (IOException);
|
||||
|
||||
/** Recursively remove all empty directories from the path rooted at
|
||||
* @p path.
|
||||
*
|
||||
* Returns true if @p path was removed.
|
||||
*/
|
||||
static bool removeEmptyDirs(const char* path);
|
||||
};
|
||||
|
||||
|
|
|
@ -31,10 +31,10 @@ void MacBundle::create(const std::string& infoPlist,
|
|||
FileUtils::mkpath(binDir.c_str());
|
||||
|
||||
// create the Contents/Info.plist file
|
||||
FileUtils::writeFile((contentDir + "/Info.plist").c_str(),infoPlist.c_str(),infoPlist.size());
|
||||
FileUtils::writeFile((contentDir + "/Info.plist").c_str(),infoPlist.c_str(),static_cast<int>(infoPlist.size()));
|
||||
|
||||
// save the icon to Contents/Resources/<appname>.icns
|
||||
FileUtils::writeFile((resourceDir + '/' + m_appName + ".icns").c_str(),icon.c_str(),icon.size());
|
||||
FileUtils::writeFile((resourceDir + '/' + m_appName + ".icns").c_str(),icon.c_str(),static_cast<int>(icon.size()));
|
||||
|
||||
// copy the app binary to Contents/MacOS/<appname>
|
||||
m_exePath = binDir + '/' + m_appName;
|
||||
|
|
|
@ -5,22 +5,31 @@
|
|||
#define PLATFORM_LINUX
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#define PLATFORM_FREEBSD
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#define PLATFORM_WINDOWS
|
||||
#include <windows.h>
|
||||
|
||||
// disable warnings about exception specifications,
|
||||
// which are not implemented in Visual C++
|
||||
#pragma warning(disable:4290)
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define PLATFORM_MAC
|
||||
#endif
|
||||
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_MAC)
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_MAC) || defined(PLATFORM_FREEBSD)
|
||||
#define PLATFORM_UNIX
|
||||
#endif
|
||||
|
||||
// platform-specific type aliases
|
||||
#if defined(PLATFORM_UNIX)
|
||||
#include <unistd.h>
|
||||
#define PLATFORM_PID pid_t
|
||||
#else
|
||||
#define PLATFORM_PID DWORD
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_FREEBSD
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_MAC
|
||||
#include <Security/Security.h>
|
||||
#include <mach-o/dyld.h>
|
||||
|
@ -87,7 +92,7 @@ int ProcessUtils::runElevated(const std::string& executable,
|
|||
#elif defined(PLATFORM_MAC)
|
||||
(void)task;
|
||||
return runElevatedMac(executable,args);
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
#elif defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
return runElevatedLinux(executable,args,task);
|
||||
#endif
|
||||
}
|
||||
|
@ -122,7 +127,7 @@ bool ProcessUtils::waitForProcess(PLATFORM_PID pid)
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifdef PLATFORM_LINUX
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
int ProcessUtils::runElevatedLinux(const std::string& executable,
|
||||
const std::list<std::string>& args,
|
||||
const std::string& _task)
|
||||
|
@ -196,6 +201,11 @@ int ProcessUtils::runElevatedLinux(const std::string& executable,
|
|||
#endif
|
||||
|
||||
#ifdef PLATFORM_MAC
|
||||
|
||||
// suppress warning about AuthorizationExecuteWithPriviledges
|
||||
// being deprecated since OS X 10.7
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
int ProcessUtils::runElevatedMac(const std::string& executable,
|
||||
const std::list<std::string>& args)
|
||||
{
|
||||
|
@ -310,6 +320,7 @@ int ProcessUtils::runElevatedMac(const std::string& executable,
|
|||
return RunElevatedFailed;
|
||||
}
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
// convert a list of arguments in a space-separated string.
|
||||
|
@ -402,10 +413,20 @@ PLATFORM_PID ProcessUtils::runAsyncUnix(const std::string& executable,
|
|||
#endif
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
int ProcessUtils::runWindows(const std::string& executable,
|
||||
int ProcessUtils::runWindows(const std::string& _executable,
|
||||
const std::list<std::string>& _args,
|
||||
RunMode runMode)
|
||||
{
|
||||
// most Windows API functions allow back and forward slashes to be
|
||||
// used interchangeably. However, an application started with
|
||||
// CreateProcess() may fail to find Side-by-Side library dependencies
|
||||
// in the same directory as the executable if forward slashes are
|
||||
// used as path separators, so convert the path to use back slashes here.
|
||||
//
|
||||
// This may be related to LoadLibrary() requiring backslashes instead
|
||||
// of forward slashes.
|
||||
std::string executable = FileUtils::toWindowsPathSeparators(_executable);
|
||||
|
||||
std::list<std::string> args(_args);
|
||||
args.push_front(executable);
|
||||
std::string commandLine = quoteArgs(args);
|
||||
|
@ -467,7 +488,23 @@ int ProcessUtils::runWindows(const std::string& executable,
|
|||
|
||||
std::string ProcessUtils::currentProcessPath()
|
||||
{
|
||||
#ifdef PLATFORM_LINUX
|
||||
#if defined(PLATFORM_FREEBSD)
|
||||
static char cmdline[PATH_MAX];
|
||||
int mib[4];
|
||||
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_ARGS;
|
||||
mib[3] = getpid();
|
||||
|
||||
size_t len = sizeof(cmdline);
|
||||
if (sysctl(mib, 4, &cmdline, &len, NULL, 0) == -1)
|
||||
{
|
||||
LOG(Error, "Could not get command line path!");
|
||||
return "";
|
||||
}
|
||||
return std::string(cmdline);
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
std::string path = FileUtils::canonicalPath("/proc/self/exe");
|
||||
LOG(Info,"Current process path " + path);
|
||||
return path;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#ifdef PLATFORM_UNIX
|
||||
#include <stdlib.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
|
@ -33,7 +34,7 @@ std::string StandardDirs::homeDir()
|
|||
std::string StandardDirs::appDataPath(const std::string& organizationName,
|
||||
const std::string& appName)
|
||||
{
|
||||
#ifdef PLATFORM_LINUX
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
std::string xdgDataHome = notNullString(getenv("XDG_DATA_HOME"));
|
||||
if (xdgDataHome.empty())
|
||||
{
|
||||
|
|
|
@ -36,7 +36,7 @@ inline const char* notNullString(const char* text)
|
|||
inline bool endsWith(const std::string& str, const char* text)
|
||||
{
|
||||
size_t length = strlen(text);
|
||||
return str.find(text,str.size() - length) != 0;
|
||||
return str.find(text,str.size() - length) != std::string::npos;
|
||||
}
|
||||
|
||||
inline bool startsWith(const std::string& str, const char* text)
|
||||
|
|
25
src/UpdateDialog.cpp
Normal file
25
src/UpdateDialog.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "UpdateDialog.h"
|
||||
|
||||
UpdateDialog::UpdateDialog()
|
||||
: m_autoClose(false)
|
||||
{
|
||||
}
|
||||
|
||||
void UpdateDialog::setAutoClose(bool autoClose)
|
||||
{
|
||||
m_autoClose = autoClose;
|
||||
}
|
||||
|
||||
bool UpdateDialog::autoClose() const
|
||||
{
|
||||
return m_autoClose;
|
||||
}
|
||||
|
||||
void UpdateDialog::updateFinished()
|
||||
{
|
||||
if (m_autoClose)
|
||||
{
|
||||
quit();
|
||||
}
|
||||
}
|
||||
|
29
src/UpdateDialog.h
Normal file
29
src/UpdateDialog.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "UpdateObserver.h"
|
||||
|
||||
/** Base class for the updater's UI, sub-classed
|
||||
* by the different platform implementations.
|
||||
*/
|
||||
class UpdateDialog : public UpdateObserver
|
||||
{
|
||||
public:
|
||||
UpdateDialog();
|
||||
virtual ~UpdateDialog() {};
|
||||
|
||||
/** Sets whether the updater should automatically
|
||||
* exit once the update has been installed.
|
||||
*/
|
||||
void setAutoClose(bool autoClose);
|
||||
bool autoClose() const;
|
||||
|
||||
virtual void init(int argc, char** argv) = 0;
|
||||
virtual void exec() = 0;
|
||||
virtual void quit() = 0;
|
||||
|
||||
virtual void updateFinished();
|
||||
|
||||
private:
|
||||
bool m_autoClose;
|
||||
};
|
||||
|
|
@ -14,7 +14,7 @@ const char* introMessage =
|
|||
"You can fix this by installing the GTK 2 libraries.\n\n"
|
||||
"Installing Updates...\n";
|
||||
|
||||
void UpdateDialogAscii::init()
|
||||
void UpdateDialogAscii::init(int /* argc */, char** /* argv */)
|
||||
{
|
||||
const char* path = "/tmp/update-progress";
|
||||
m_output.open(path);
|
||||
|
@ -56,6 +56,15 @@ void UpdateDialogAscii::updateFinished()
|
|||
m_mutex.lock();
|
||||
m_output << "\nUpdate Finished. You can now restart " << AppInfo::appName() << "." << std::endl;
|
||||
m_mutex.unlock();
|
||||
|
||||
UpdateDialog::updateFinished();
|
||||
}
|
||||
|
||||
void UpdateDialogAscii::quit()
|
||||
{
|
||||
}
|
||||
|
||||
void UpdateDialogAscii::exec()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "UpdateObserver.h"
|
||||
#include "UpdateDialog.h"
|
||||
|
||||
#include <fstream>
|
||||
#include "tinythread.h"
|
||||
|
@ -11,11 +11,15 @@
|
|||
* The 'dialog' consists of an xterm tailing the contents
|
||||
* of a file, into which progress messages are written.
|
||||
*/
|
||||
class UpdateDialogAscii : public UpdateObserver
|
||||
class UpdateDialogAscii : public UpdateDialog
|
||||
{
|
||||
public:
|
||||
void init();
|
||||
// implements UpdateDialog
|
||||
virtual void init(int argc, char** argv);
|
||||
virtual void exec();
|
||||
virtual void quit();
|
||||
|
||||
// implements UpdateObserver
|
||||
virtual void updateError(const std::string& errorMessage);
|
||||
virtual void updateProgress(int percentage);
|
||||
virtual void updateFinished();
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "UpdateDialog.h"
|
||||
#include "UpdateObserver.h"
|
||||
|
||||
class UpdateDialogPrivate;
|
||||
|
||||
class UpdateDialogCocoa : public UpdateObserver
|
||||
class UpdateDialogCocoa : public UpdateDialog
|
||||
{
|
||||
public:
|
||||
UpdateDialogCocoa();
|
||||
~UpdateDialogCocoa();
|
||||
|
||||
void init();
|
||||
void exec();
|
||||
// implements UpdateDialog
|
||||
virtual void init(int argc, char** argv);
|
||||
virtual void exec();
|
||||
virtual void quit();
|
||||
|
||||
// implements UpdateObserver
|
||||
virtual void updateError(const std::string& errorMessage);
|
||||
|
|
|
@ -42,18 +42,14 @@ class UpdateDialogPrivate
|
|||
- (void) reportUpdateError: (id)arg
|
||||
{
|
||||
dialog->hadError = true;
|
||||
NSMutableString* message = [[NSMutableString alloc] init];
|
||||
[message appendString:@"There was a problem installing the update:\n\n"];
|
||||
[message appendString:arg];
|
||||
|
||||
NSAlert* alert = [NSAlert
|
||||
alertWithMessageText: @"Update Problem"
|
||||
defaultButton: nil
|
||||
alternateButton: nil
|
||||
otherButton: nil
|
||||
informativeTextWithFormat: message];
|
||||
informativeTextWithFormat: @"There was a problem installing the update:\n\n%@", arg];
|
||||
[alert runModal];
|
||||
[message release];
|
||||
}
|
||||
- (void) reportUpdateProgress: (id)arg
|
||||
{
|
||||
|
@ -102,7 +98,7 @@ void UpdateDialogCocoa::enableDockIcon()
|
|||
TransformProcessType(&psn,kProcessTransformToForegroundApplication);
|
||||
}
|
||||
|
||||
void UpdateDialogCocoa::init()
|
||||
void UpdateDialogCocoa::init(int /* argc */, char** /* argv */)
|
||||
{
|
||||
enableDockIcon();
|
||||
|
||||
|
@ -177,6 +173,7 @@ void UpdateDialogCocoa::updateFinished()
|
|||
[d->delegate performSelectorOnMainThread:@selector(reportUpdateFinished:)
|
||||
withObject:nil
|
||||
waitUntilDone:false];
|
||||
UpdateDialog::updateFinished();
|
||||
}
|
||||
|
||||
void* UpdateDialogCocoa::createAutoreleasePool()
|
||||
|
@ -189,4 +186,9 @@ void UpdateDialogCocoa::releaseAutoreleasePool(void* arg)
|
|||
[(id)arg release];
|
||||
}
|
||||
|
||||
void UpdateDialogCocoa::quit()
|
||||
{
|
||||
[NSApp performSelectorOnMainThread:@selector(stop:) withObject:d->delegate waitUntilDone:false];
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,31 +6,9 @@
|
|||
#include <glib.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
UpdateDialogGtk* update_dialog_gtk_new(int argc, char** argv)
|
||||
UpdateDialogGtk* update_dialog_gtk_new()
|
||||
{
|
||||
UpdateDialogGtk* dialog = new UpdateDialogGtk;
|
||||
dialog->init(argc,argv);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
void update_dialog_gtk_exec(UpdateDialogGtk* dialog)
|
||||
{
|
||||
dialog->exec();
|
||||
}
|
||||
|
||||
void update_dialog_gtk_handle_error(UpdateDialogGtk* dialog, const std::string& errorMessage)
|
||||
{
|
||||
dialog->updateError(errorMessage);
|
||||
}
|
||||
|
||||
void update_dialog_gtk_handle_progress(UpdateDialogGtk* dialog, int percentage)
|
||||
{
|
||||
dialog->updateProgress(percentage);
|
||||
}
|
||||
|
||||
void update_dialog_gtk_handle_finished(UpdateDialogGtk* dialog)
|
||||
{
|
||||
dialog->updateFinished();
|
||||
return new UpdateDialogGtk();
|
||||
}
|
||||
|
||||
UpdateDialogGtk::UpdateDialogGtk()
|
||||
|
@ -95,6 +73,12 @@ void UpdateDialogGtk::exec()
|
|||
}
|
||||
|
||||
void UpdateDialogGtk::finish(GtkWidget* widget, gpointer _dialog)
|
||||
{
|
||||
UpdateDialogGtk* dialog = static_cast<UpdateDialogGtk*>(_dialog);
|
||||
dialog->quit();
|
||||
}
|
||||
|
||||
void UpdateDialogGtk::quit()
|
||||
{
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
@ -165,6 +149,7 @@ void UpdateDialogGtk::updateFinished()
|
|||
{
|
||||
UpdateMessage* message = new UpdateMessage(this,UpdateMessage::UpdateFinished);
|
||||
g_idle_add(&UpdateDialogGtk::notify,message);
|
||||
UpdateDialog::updateFinished();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "UpdateDialog.h"
|
||||
#include "UpdateMessage.h"
|
||||
#include "UpdateObserver.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
class UpdateDialogGtk : public UpdateObserver
|
||||
class UpdateDialogGtk : public UpdateDialog
|
||||
{
|
||||
public:
|
||||
UpdateDialogGtk();
|
||||
|
||||
void init(int argc, char** argv);
|
||||
void exec();
|
||||
// implements UpdateDialog
|
||||
virtual void init(int argc, char** argv);
|
||||
virtual void exec();
|
||||
virtual void quit();
|
||||
|
||||
// observer callbacks - these may be called
|
||||
// from a background thread
|
||||
|
@ -33,11 +36,7 @@ class UpdateDialogGtk : public UpdateObserver
|
|||
// helper functions which allow the GTK dialog to be loaded dynamically
|
||||
// at runtime and used only if the GTK libraries are actually present
|
||||
extern "C" {
|
||||
UpdateDialogGtk* update_dialog_gtk_new(int argc, char** argv);
|
||||
void update_dialog_gtk_exec(UpdateDialogGtk* dialog);
|
||||
void update_dialog_gtk_handle_error(UpdateDialogGtk* dialog, const std::string& errorMessage);
|
||||
void update_dialog_gtk_handle_progress(UpdateDialogGtk* dialog, int percentage);
|
||||
void update_dialog_gtk_handle_finished(UpdateDialogGtk* dialog);
|
||||
UpdateDialogGtk* update_dialog_gtk_new();
|
||||
}
|
||||
|
||||
|
||||
|
|
72
src/UpdateDialogGtkFactory.cpp
Normal file
72
src/UpdateDialogGtkFactory.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include "UpdateDialogGtkFactory.h"
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "Log.h"
|
||||
#include "UpdateDialog.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class UpdateDialogGtk;
|
||||
|
||||
// GTK updater UI library embedded into
|
||||
// the updater binary
|
||||
extern unsigned char libupdatergtk_so[];
|
||||
extern unsigned int libupdatergtk_so_len;
|
||||
|
||||
// pointers to helper functions in the GTK updater UI library
|
||||
UpdateDialogGtk* (*update_dialog_gtk_new)() = 0;
|
||||
|
||||
#if __cplusplus >= 201103L
|
||||
#define TYPEOF(x) decltype(x)
|
||||
#else
|
||||
#define TYPEOF(x) typeof(x)
|
||||
#endif
|
||||
|
||||
#define BIND_FUNCTION(library,function) \
|
||||
function = reinterpret_cast<TYPEOF(function)>(dlsym(library,#function));
|
||||
|
||||
#define MAX_FILE_PATH 4096
|
||||
|
||||
bool extractFileFromBinary(int fd, const void* buffer, size_t length)
|
||||
{
|
||||
size_t count = write(fd,buffer,length);
|
||||
return count >= length;
|
||||
}
|
||||
|
||||
UpdateDialog* UpdateDialogGtkFactory::createDialog()
|
||||
{
|
||||
char libPath[MAX_FILE_PATH];
|
||||
strncpy(libPath, "/tmp/mendeley-libUpdaterGtk.so.XXXXXX", MAX_FILE_PATH);
|
||||
|
||||
int libFd = mkostemp(libPath, O_CREAT | O_WRONLY | O_TRUNC);
|
||||
if (libFd == -1)
|
||||
{
|
||||
LOG(Warn,"Failed to create temporary file - " + std::string(strerror(errno)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!extractFileFromBinary(libFd,libupdatergtk_so,libupdatergtk_so_len))
|
||||
{
|
||||
LOG(Warn,"Failed to load the GTK UI library - " + std::string(strerror(errno)));
|
||||
return 0;
|
||||
}
|
||||
close(libFd);
|
||||
|
||||
void* gtkLib = dlopen(libPath,RTLD_LAZY);
|
||||
if (!gtkLib)
|
||||
{
|
||||
LOG(Warn,"Failed to load the GTK UI - " + std::string(dlerror()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
BIND_FUNCTION(gtkLib,update_dialog_gtk_new);
|
||||
|
||||
FileUtils::removeFile(libPath);
|
||||
return reinterpret_cast<UpdateDialog*>(update_dialog_gtk_new());
|
||||
}
|
13
src/UpdateDialogGtkFactory.h
Normal file
13
src/UpdateDialogGtkFactory.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
class UpdateDialog;
|
||||
|
||||
/** Factory for loading the GTK version of the update dialog
|
||||
* dynamically at runtime if the GTK libraries are available.
|
||||
*/
|
||||
class UpdateDialogGtkFactory
|
||||
{
|
||||
public:
|
||||
static UpdateDialog* createDialog();
|
||||
};
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
#include "UpdateDialogGtkWrapper.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// GTK updater UI library embedded into
|
||||
// the updater binary
|
||||
extern unsigned char libupdatergtk_so[];
|
||||
extern unsigned int libupdatergtk_so_len;
|
||||
|
||||
// pointers to helper functions in the GTK updater UI library
|
||||
UpdateDialogGtk* (*update_dialog_gtk_new)(int,char**) = 0;
|
||||
void (*update_dialog_gtk_exec)(UpdateDialogGtk* dialog) = 0;
|
||||
void (*update_dialog_gtk_handle_error)(UpdateDialogGtk* dialog, const std::string& errorMessage) = 0;
|
||||
void (*update_dialog_gtk_handle_progress)(UpdateDialogGtk* dialog, int percentage) = 0;
|
||||
void (*update_dialog_gtk_handle_finished)(UpdateDialogGtk* dialog) = 0;
|
||||
|
||||
#define BIND_FUNCTION(library,function) \
|
||||
function = reinterpret_cast<typeof(function)>(dlsym(library,#function));
|
||||
|
||||
bool extractFileFromBinary(const char* path, const void* buffer, size_t length)
|
||||
{
|
||||
int fd = open(path,O_CREAT | O_WRONLY | O_TRUNC,0755);
|
||||
size_t count = write(fd,buffer,length);
|
||||
if (fd < 0 || count < length)
|
||||
{
|
||||
if (fd >= 0)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
UpdateDialogGtkWrapper::UpdateDialogGtkWrapper()
|
||||
: m_dialog(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool UpdateDialogGtkWrapper::init(int argc, char** argv)
|
||||
{
|
||||
const char* libPath = "/tmp/libupdatergtk.so";
|
||||
|
||||
if (!extractFileFromBinary(libPath,libupdatergtk_so,libupdatergtk_so_len))
|
||||
{
|
||||
LOG(Warn,"Failed to load the GTK UI library - " + std::string(strerror(errno)));
|
||||
}
|
||||
|
||||
void* gtkLib = dlopen(libPath,RTLD_LAZY);
|
||||
if (!gtkLib)
|
||||
{
|
||||
LOG(Warn,"Failed to load the GTK UI - " + std::string(dlerror()));
|
||||
return false;
|
||||
}
|
||||
|
||||
BIND_FUNCTION(gtkLib,update_dialog_gtk_new);
|
||||
BIND_FUNCTION(gtkLib,update_dialog_gtk_exec);
|
||||
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_error);
|
||||
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_progress);
|
||||
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_finished);
|
||||
|
||||
m_dialog = update_dialog_gtk_new(argc,argv);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateDialogGtkWrapper::exec()
|
||||
{
|
||||
update_dialog_gtk_exec(m_dialog);
|
||||
}
|
||||
|
||||
void UpdateDialogGtkWrapper::updateError(const std::string& errorMessage)
|
||||
{
|
||||
update_dialog_gtk_handle_error(m_dialog,errorMessage);
|
||||
}
|
||||
|
||||
void UpdateDialogGtkWrapper::updateProgress(int percentage)
|
||||
{
|
||||
update_dialog_gtk_handle_progress(m_dialog,percentage);
|
||||
}
|
||||
|
||||
void UpdateDialogGtkWrapper::updateFinished()
|
||||
{
|
||||
update_dialog_gtk_handle_finished(m_dialog);
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "UpdateObserver.h"
|
||||
|
||||
class UpdateDialogGtk;
|
||||
|
||||
/** A wrapper around UpdateDialogGtk which allows the GTK UI to
|
||||
* be loaded dynamically at runtime if the GTK libraries are
|
||||
* available.
|
||||
*/
|
||||
class UpdateDialogGtkWrapper : public UpdateObserver
|
||||
{
|
||||
public:
|
||||
UpdateDialogGtkWrapper();
|
||||
|
||||
/** Attempt to load and initialize the GTK updater UI.
|
||||
* If this function returns false, other calls in UpdateDialogGtkWrapper
|
||||
* may not be used.
|
||||
*/
|
||||
bool init(int argc, char** argv);
|
||||
void exec();
|
||||
|
||||
virtual void updateError(const std::string& errorMessage);
|
||||
virtual void updateProgress(int percentage);
|
||||
virtual void updateFinished();
|
||||
|
||||
private:
|
||||
UpdateDialogGtk* m_dialog;
|
||||
};
|
||||
|
|
@ -83,7 +83,7 @@ UpdateDialogWin32::~UpdateDialogWin32()
|
|||
}
|
||||
}
|
||||
|
||||
void UpdateDialogWin32::init()
|
||||
void UpdateDialogWin32::init(int /* argc */, char** /* argv */)
|
||||
{
|
||||
int width = 300;
|
||||
int height = 130;
|
||||
|
@ -141,6 +141,12 @@ void UpdateDialogWin32::updateFinished()
|
|||
{
|
||||
UpdateMessage* message = new UpdateMessage(UpdateMessage::UpdateFinished);
|
||||
SendNotifyMessage(m_window.GetHwnd(),WM_USER,reinterpret_cast<WPARAM>(message),0);
|
||||
UpdateDialog::updateFinished();
|
||||
}
|
||||
|
||||
void UpdateDialogWin32::quit()
|
||||
{
|
||||
PostThreadMessage(GetWindowThreadProcessId(m_window.GetHwnd(), 0 /* process ID */), WM_QUIT, 0, 0);
|
||||
}
|
||||
|
||||
LRESULT WINAPI UpdateDialogWin32::windowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
|
@ -157,7 +163,7 @@ LRESULT WINAPI UpdateDialogWin32::windowProc(HWND window, UINT message, WPARAM w
|
|||
{
|
||||
if (reinterpret_cast<HWND>(lParam) == m_finishButton.GetHwnd())
|
||||
{
|
||||
PostQuitMessage(0);
|
||||
quit();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "Platform.h"
|
||||
#include "UpdateDialog.h"
|
||||
#include "UpdateMessage.h"
|
||||
#include "UpdateObserver.h"
|
||||
|
||||
#include "wincore.h"
|
||||
#include "controls.h"
|
||||
#include "stdcontrols.h"
|
||||
|
||||
class UpdateDialogWin32 : public UpdateObserver
|
||||
class UpdateDialogWin32 : public UpdateDialog
|
||||
{
|
||||
public:
|
||||
UpdateDialogWin32();
|
||||
~UpdateDialogWin32();
|
||||
|
||||
void init();
|
||||
void exec();
|
||||
// implements UpdateDialog
|
||||
virtual void init(int argc, char** argv);
|
||||
virtual void exec();
|
||||
virtual void quit();
|
||||
|
||||
// implements UpdateObserver
|
||||
virtual void updateError(const std::string& errorMessage);
|
||||
|
|
|
@ -12,6 +12,7 @@ UpdateInstaller::UpdateInstaller()
|
|||
, m_script(0)
|
||||
, m_observer(0)
|
||||
, m_forceElevated(false)
|
||||
, m_autoClose(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -59,6 +60,10 @@ std::list<std::string> UpdateInstaller::updaterArgs() const
|
|||
args.push_back(m_packageDir);
|
||||
args.push_back("--script");
|
||||
args.push_back(m_script->path());
|
||||
if (m_autoClose)
|
||||
{
|
||||
args.push_back("--auto-close");
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
|
@ -115,7 +120,7 @@ void UpdateInstaller::run() throw ()
|
|||
{
|
||||
updaterPath = ProcessUtils::currentProcessPath();
|
||||
}
|
||||
catch (const FileUtils::IOException& ex)
|
||||
catch (const FileUtils::IOException&)
|
||||
{
|
||||
LOG(Error,"error reading process path with mode " + intToStr(m_mode));
|
||||
reportError("Unable to determine path of updater");
|
||||
|
@ -271,7 +276,7 @@ void UpdateInstaller::installFile(const UpdateScriptFile& file)
|
|||
std::string destDir = FileUtils::dirname(destPath.c_str());
|
||||
if (!FileUtils::fileExists(destDir.c_str()))
|
||||
{
|
||||
FileUtils::mkdir(destDir.c_str());
|
||||
FileUtils::mkpath(destDir.c_str());
|
||||
}
|
||||
|
||||
if (target.empty())
|
||||
|
@ -407,30 +412,31 @@ void UpdateInstaller::restartMainApp()
|
|||
std::string command;
|
||||
std::list<std::string> args;
|
||||
|
||||
for (std::vector<UpdateScriptFile>::const_iterator iter = m_script->filesToInstall().begin();
|
||||
iter != m_script->filesToInstall().end();
|
||||
iter++)
|
||||
if (!m_executable.empty())
|
||||
{
|
||||
if (iter->isMainBinary)
|
||||
command = m_installDir + '/' + m_executable;
|
||||
if (!m_executable_args.empty())
|
||||
{
|
||||
command = m_installDir + '/' + iter->path;
|
||||
args.push_front(m_executable_args);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::vector<UpdateScriptFile>::const_iterator iter = m_script->filesToInstall().begin();
|
||||
iter != m_script->filesToInstall().end();
|
||||
iter++)
|
||||
{
|
||||
if (iter->isMainBinary)
|
||||
{
|
||||
command = m_installDir + '/' + iter->path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!command.empty())
|
||||
{
|
||||
LOG(Info,"Starting main application " + command);
|
||||
|
||||
// change the current directory to that of the application binary,
|
||||
// so that on Windows the application can find shared libraries
|
||||
// that it depends on which are in the same directory
|
||||
std::string appDir = FileUtils::dirname(command.c_str());
|
||||
std::string currentDir = FileUtils::getcwd();
|
||||
FileUtils::chdir(appDir.c_str());
|
||||
|
||||
ProcessUtils::runAsync(command,args);
|
||||
|
||||
FileUtils::chdir(currentDir.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -453,5 +459,23 @@ void UpdateInstaller::postInstallUpdate()
|
|||
// Info.plist file.
|
||||
FileUtils::touch(m_installDir.c_str());
|
||||
#endif
|
||||
|
||||
// recursively remove any empty directories in the installation dir
|
||||
// remove any empty directories in the installation dir
|
||||
FileUtils::removeEmptyDirs(m_installDir.c_str());
|
||||
}
|
||||
|
||||
void UpdateInstaller::setAutoClose(bool autoClose)
|
||||
{
|
||||
m_autoClose = autoClose;
|
||||
}
|
||||
|
||||
void UpdateInstaller::setExecutable(std::string& bin)
|
||||
{
|
||||
m_executable = bin;
|
||||
}
|
||||
|
||||
void UpdateInstaller::setExecutableArgs(std::string& args)
|
||||
{
|
||||
m_executable_args = args;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ class UpdateInstaller
|
|||
void setScript(UpdateScript* script);
|
||||
void setWaitPid(PLATFORM_PID pid);
|
||||
void setForceElevated(bool elevated);
|
||||
void setAutoClose(bool autoClose);
|
||||
void setExecutable(std::string& bin);
|
||||
void setExecutableArgs(std::string& args);
|
||||
|
||||
void setObserver(UpdateObserver* observer);
|
||||
|
||||
|
@ -64,5 +67,8 @@ class UpdateInstaller
|
|||
UpdateObserver* m_observer;
|
||||
std::map<std::string,std::string> m_backups;
|
||||
bool m_forceElevated;
|
||||
bool m_autoClose;
|
||||
std::string m_executable;
|
||||
std::string m_executable_args;
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
#if defined(PLATFORM_WINDOWS) && (_MSC_VER < 1800)
|
||||
// atoll() was added in MSVC 2013
|
||||
long long atoll(const char* string)
|
||||
{
|
||||
return _atoi64(string);
|
||||
|
@ -21,6 +22,7 @@ UpdaterOptions::UpdaterOptions()
|
|||
, waitPid(0)
|
||||
, showVersion(false)
|
||||
, forceElevated(false)
|
||||
, autoClose(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -69,7 +71,7 @@ void UpdaterOptions::parseOldFormatArgs(int argc, char** argv)
|
|||
// binary. On Mac and Linux this differs from the root of
|
||||
// the installation directory
|
||||
|
||||
#ifdef PLATFORM_LINUX
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
// the main binary is in lib/mendeleydesktop/libexec,
|
||||
// go up 3 levels
|
||||
installDir = FileUtils::canonicalPath((value + "/../../../").c_str());
|
||||
|
@ -113,8 +115,11 @@ void UpdaterOptions::parse(int argc, char** argv)
|
|||
parser.setOption("script");
|
||||
parser.setOption("wait");
|
||||
parser.setOption("mode");
|
||||
parser.setOption("execute");
|
||||
parser.setOption("execute-args");
|
||||
parser.setFlag("version");
|
||||
parser.setFlag("force-elevated");
|
||||
parser.setFlag("auto-close");
|
||||
|
||||
parser.processCommandArgs(argc,argv);
|
||||
|
||||
|
@ -138,9 +143,18 @@ void UpdaterOptions::parse(int argc, char** argv)
|
|||
{
|
||||
waitPid = static_cast<PLATFORM_PID>(atoll(parser.getValue("wait")));
|
||||
}
|
||||
if (parser.getValue("execute"))
|
||||
{
|
||||
executable = parser.getValue("execute");
|
||||
}
|
||||
if (parser.getValue("execute-args"))
|
||||
{
|
||||
executable_args = parser.getValue("execute-args");
|
||||
}
|
||||
|
||||
showVersion = parser.getFlag("version");
|
||||
forceElevated = parser.getFlag("force-elevated");
|
||||
autoClose = parser.getFlag("auto-close");
|
||||
|
||||
if (installDir.empty())
|
||||
{
|
||||
|
|
|
@ -14,10 +14,13 @@ class UpdaterOptions
|
|||
std::string installDir;
|
||||
std::string packageDir;
|
||||
std::string scriptPath;
|
||||
std::string executable;
|
||||
std::string executable_args;
|
||||
PLATFORM_PID waitPid;
|
||||
std::string logFile;
|
||||
bool showVersion;
|
||||
bool forceElevated;
|
||||
bool autoClose;
|
||||
|
||||
private:
|
||||
void parseOldFormatArgs(int argc, char** argv);
|
||||
|
|
83
src/main.cpp
83
src/main.cpp
|
@ -9,8 +9,8 @@
|
|||
|
||||
#include "tinythread.h"
|
||||
|
||||
#if defined(PLATFORM_LINUX)
|
||||
#include "UpdateDialogGtkWrapper.h"
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
#include "UpdateDialogGtkFactory.h"
|
||||
#include "UpdateDialogAscii.h"
|
||||
#endif
|
||||
|
||||
|
@ -24,10 +24,11 @@
|
|||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#define UPDATER_VERSION "0.7"
|
||||
#define UPDATER_VERSION "0.16"
|
||||
|
||||
void runWithUi(int argc, char** argv, UpdateInstaller* installer);
|
||||
UpdateDialog* createUpdateDialog();
|
||||
|
||||
void runUpdaterThread(void* arg)
|
||||
{
|
||||
|
@ -86,6 +87,17 @@ bool unpackBundle(int argc, char** argv)
|
|||
}
|
||||
#endif
|
||||
|
||||
void setupConsole()
|
||||
{
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
// see http://stackoverflow.com/questions/587767/how-to-output-to-console-in-c-windows
|
||||
// and http://www.libsdl.org/cgi/docwiki.cgi/FAQ_Console
|
||||
AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
freopen( "CON", "w", stdout );
|
||||
freopen( "CON", "w", stderr );
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
#ifdef PLATFORM_MAC
|
||||
|
@ -108,6 +120,7 @@ int main(int argc, char** argv)
|
|||
options.parse(argc,argv);
|
||||
if (options.showVersion)
|
||||
{
|
||||
setupConsole();
|
||||
std::cout << "Update installer version " << UPDATER_VERSION << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
@ -132,10 +145,20 @@ int main(int argc, char** argv)
|
|||
installer.setScript(&script);
|
||||
installer.setWaitPid(options.waitPid);
|
||||
installer.setForceElevated(options.forceElevated);
|
||||
installer.setAutoClose(options.autoClose);
|
||||
installer.setExecutable(options.executable);
|
||||
installer.setExecutableArgs(options.executable_args);
|
||||
|
||||
if (options.mode == UpdateInstaller::Main)
|
||||
{
|
||||
runWithUi(argc,argv,&installer);
|
||||
LOG(Info, "Showing updater UI - auto close? " + intToStr(options.autoClose));
|
||||
std::auto_ptr<UpdateDialog> dialog(createUpdateDialog());
|
||||
dialog->setAutoClose(options.autoClose);
|
||||
dialog->init(argc, argv);
|
||||
installer.setObserver(dialog.get());
|
||||
tthread::thread updaterThread(runUpdaterThread, &installer);
|
||||
dialog->exec();
|
||||
updaterThread.join();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -149,41 +172,21 @@ int main(int argc, char** argv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef PLATFORM_LINUX
|
||||
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
|
||||
UpdateDialog* createUpdateDialog()
|
||||
{
|
||||
UpdateDialogAscii asciiDialog;
|
||||
UpdateDialogGtkWrapper dialog;
|
||||
bool useGtk = dialog.init(argc,argv);
|
||||
if (useGtk)
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
return new UpdateDialogWin32();
|
||||
#elif defined(PLATFORM_MAC)
|
||||
return new UpdateDialogCocoa();
|
||||
#elif defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
UpdateDialog* dialog = UpdateDialogGtkFactory::createDialog();
|
||||
if (!dialog)
|
||||
{
|
||||
installer->setObserver(&dialog);
|
||||
dialog = new UpdateDialogAscii();
|
||||
}
|
||||
else
|
||||
{
|
||||
asciiDialog.init();
|
||||
installer->setObserver(&asciiDialog);
|
||||
}
|
||||
tthread::thread updaterThread(runUpdaterThread,installer);
|
||||
if (useGtk)
|
||||
{
|
||||
dialog.exec();
|
||||
}
|
||||
updaterThread.join();
|
||||
}
|
||||
return dialog;
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_MAC
|
||||
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
|
||||
{
|
||||
UpdateDialogCocoa dialog;
|
||||
installer->setObserver(&dialog);
|
||||
dialog.init();
|
||||
tthread::thread updaterThread(runUpdaterThread,installer);
|
||||
dialog.exec();
|
||||
updaterThread.join();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
// application entry point under Windows
|
||||
|
@ -197,14 +200,4 @@ int CALLBACK WinMain(HINSTANCE hInstance,
|
|||
ProcessUtils::convertWindowsCommandLine(GetCommandLineW(),argc,argv);
|
||||
return main(argc,argv);
|
||||
}
|
||||
|
||||
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
|
||||
{
|
||||
UpdateDialogWin32 dialog;
|
||||
installer->setObserver(&dialog);
|
||||
dialog.init();
|
||||
tthread::thread updaterThread(runUpdaterThread,installer);
|
||||
dialog.exec();
|
||||
updaterThread.join();
|
||||
}
|
||||
#endif
|
||||
|
|
BIN
src/resources/etl.ico
Normal file
BIN
src/resources/etl.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 361 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 70 KiB |
|
@ -1,4 +1,4 @@
|
|||
IDI_APPICON ICON DISCARDABLE "updater.ico"
|
||||
IDI_APPICON ICON DISCARDABLE "etl.ico"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,0,1,0
|
||||
|
@ -18,9 +18,9 @@ BEGIN
|
|||
VALUE "OriginalFilename", "updater.exe"
|
||||
VALUE "InternalName", "updater.exe"
|
||||
VALUE "FileDescription", "Software Update Tool"
|
||||
VALUE "CompanyName", "Mendeley Ltd."
|
||||
VALUE "LegalCopyright", "(C) Mendeley Ltd. 2011"
|
||||
VALUE "ProductName", "Mendeley Software Updater"
|
||||
VALUE "CompanyName", "Legacy Team."
|
||||
VALUE "LegalCopyright", "© 2011-2014 Legacy Team"
|
||||
VALUE "ProductName", "Legacy Updater"
|
||||
VALUE "PrivateBuild", "Built by Robert Knight"
|
||||
END
|
||||
END
|
||||
|
|
|
@ -24,21 +24,22 @@ foreach(TEST_FILE ${TEST_FILES})
|
|||
endforeach()
|
||||
|
||||
# Add unit test binaries
|
||||
add_executable(TestUpdateScript
|
||||
TestUpdateScript.cpp
|
||||
)
|
||||
target_link_libraries(TestUpdateScript
|
||||
updatershared
|
||||
)
|
||||
add_executable(TestUpdaterOptions
|
||||
TestUpdaterOptions.cpp
|
||||
)
|
||||
target_link_libraries(TestUpdaterOptions
|
||||
updatershared
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
set_target_properties(TestUpdateScript PROPERTIES LINK_FLAGS "-framework Security -framework Cocoa")
|
||||
set_target_properties(TestUpdaterOptions PROPERTIES LINK_FLAGS "-framework Security -framework Cocoa")
|
||||
endif()
|
||||
macro(ADD_UPDATER_TEST CLASS)
|
||||
set(TEST_TARGET updater_${CLASS})
|
||||
add_executable(${TEST_TARGET} ${CLASS}.cpp)
|
||||
target_link_libraries(${TEST_TARGET} updatershared)
|
||||
add_test(${TEST_TARGET} ${TEST_TARGET})
|
||||
if (APPLE)
|
||||
set_target_properties(${TEST_TARGET} PROPERTIES LINK_FLAGS "-framework Security -framework Cocoa")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
add_updater_test(TestUpdateScript)
|
||||
add_updater_test(TestUpdaterOptions)
|
||||
add_updater_test(TestFileUtils)
|
||||
|
||||
# Add updater that that performs a complete update install
|
||||
# and checks the result
|
||||
find_program(RUBY_BIN ruby)
|
||||
add_test(updater_TestUpdateInstall ${RUBY_BIN} test-update.rb)
|
||||
|
||||
|
|
69
src/tests/TestFileUtils.cpp
Normal file
69
src/tests/TestFileUtils.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "TestFileUtils.h"
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
void TestFileUtils::testDirName()
|
||||
{
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
std::string dirName = FileUtils::dirname("E:/Some Dir/App.exe");
|
||||
TEST_COMPARE(dirName,"E:/Some Dir/");
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestFileUtils::testIsRelative()
|
||||
{
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
TEST_COMPARE(FileUtils::isRelative("temp"),true);
|
||||
TEST_COMPARE(FileUtils::isRelative("D:/temp"),false);
|
||||
TEST_COMPARE(FileUtils::isRelative("d:/temp"),false);
|
||||
#else
|
||||
TEST_COMPARE(FileUtils::isRelative("/tmp"),false);
|
||||
TEST_COMPARE(FileUtils::isRelative("tmp"),true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestFileUtils::testSymlinkFileExists()
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
const char* linkName = "link-name";
|
||||
FileUtils::removeFile(linkName);
|
||||
FileUtils::createSymLink(linkName, "target-that-does-not-exist");
|
||||
TEST_COMPARE(FileUtils::fileExists(linkName), true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestFileUtils::testStandardDirs()
|
||||
{
|
||||
std::string tmpDir = FileUtils::tempPath();
|
||||
TEST_COMPARE(FileUtils::fileExists(tmpDir.data()), true);
|
||||
}
|
||||
|
||||
void TestFileUtils::testRemoveEmptyDirs()
|
||||
{
|
||||
std::string tmpDir = FileUtils::tempPath();
|
||||
std::string rootDir = tmpDir + "/TestFileUtils-testRemoveEmptyDirs";
|
||||
std::string content = "non-empty-file-content";
|
||||
|
||||
FileUtils::mkpath((rootDir + "/nested/empty/dir").c_str());
|
||||
FileUtils::mkpath((rootDir + "/nested/empty2/dir").c_str());
|
||||
FileUtils::writeFile((rootDir + "/nonempty.txt").c_str(), content.c_str(), content.size());
|
||||
FileUtils::removeEmptyDirs(rootDir.c_str());
|
||||
|
||||
// root dir and the regular file should still exist
|
||||
TEST_COMPARE(FileUtils::fileExists(rootDir.c_str()), true);
|
||||
TEST_COMPARE(FileUtils::fileExists((rootDir + "/nonempty.txt").c_str()), true);
|
||||
|
||||
// the empty nested directories should have been removed
|
||||
TEST_COMPARE(FileUtils::fileExists((rootDir + "/nested").c_str()), true);
|
||||
}
|
||||
|
||||
int main(int,char**)
|
||||
{
|
||||
TestList<TestFileUtils> tests;
|
||||
tests.addTest(&TestFileUtils::testDirName);
|
||||
tests.addTest(&TestFileUtils::testIsRelative);
|
||||
tests.addTest(&TestFileUtils::testSymlinkFileExists);
|
||||
tests.addTest(&TestFileUtils::testStandardDirs);
|
||||
return TestUtils::runTest(tests);
|
||||
}
|
11
src/tests/TestFileUtils.h
Normal file
11
src/tests/TestFileUtils.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
class TestFileUtils
|
||||
{
|
||||
public:
|
||||
void testDirName();
|
||||
void testIsRelative();
|
||||
void testSymlinkFileExists();
|
||||
void testStandardDirs();
|
||||
void testRemoveEmptyDirs();
|
||||
};
|
|
@ -20,7 +20,7 @@ void TestUpdaterOptions::testOldFormatArgs()
|
|||
// CurrentDir is the path to the directory containing the main
|
||||
// Mendeley Desktop binary, on Linux and Mac this differs from
|
||||
// the root of the install directory
|
||||
#ifdef PLATFORM_LINUX
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
appDir = "/tmp/path-to-app/lib/mendeleydesktop/libexec/";
|
||||
FileUtils::mkpath(appDir);
|
||||
#elif defined(PLATFORM_MAC)
|
||||
|
@ -41,7 +41,7 @@ void TestUpdaterOptions::testOldFormatArgs()
|
|||
options.parse(argc,argv);
|
||||
|
||||
TEST_COMPARE(options.mode,UpdateInstaller::Setup);
|
||||
#ifdef PLATFORM_LINUX
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
|
||||
TEST_COMPARE(options.installDir,"/tmp/path-to-app");
|
||||
#elif defined(PLATFORM_MAC)
|
||||
// /tmp is a symlink to /private/tmp on Mac
|
||||
|
|
|
@ -105,3 +105,4 @@ inline std::string TestUtils::toString(const char* value, const char*)
|
|||
#define TEST_COMPARE(x,y) \
|
||||
TestUtils::compare(x,y,#x,#y);
|
||||
|
||||
|
||||
|
|
|
@ -35,9 +35,18 @@
|
|||
<name>test-dir/app-symlink</name>
|
||||
<target>../app</target>
|
||||
</file>
|
||||
<!-- Test file in new directory !-->
|
||||
<file>
|
||||
<name>new-dir/new-dir2/new-file.txt</name>
|
||||
<hash>$TEST_FILENAME</hash>
|
||||
<size>$TEST_SIZE</size>
|
||||
<package>app-pkg</package>
|
||||
<permissions>0644</permissions>
|
||||
</file>
|
||||
</install>
|
||||
<uninstall>
|
||||
<!-- TODO - List some files to uninstall here !-->
|
||||
<file>file-to-uninstall.txt</file>
|
||||
<file>symlink-to-file-to-uninstall.txt</file>
|
||||
<file>will-become-empty-after-update/nested/file-to-uninstall.txt</file>
|
||||
</uninstall>
|
||||
</update>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/ruby
|
||||
|
||||
require 'fileutils.rb'
|
||||
require 'find'
|
||||
require 'rbconfig'
|
||||
require 'optparse'
|
||||
|
||||
|
@ -9,19 +10,21 @@ require 'optparse'
|
|||
# line arguments under Windows
|
||||
INSTALL_DIR = File.expand_path("install dir/")
|
||||
PACKAGE_DIR = File.expand_path("package-dir/")
|
||||
PACKAGE_SRC_DIR = File.expand_path("package-src-dir/")
|
||||
IS_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
||||
|
||||
if (RbConfig::CONFIG['host_os'] =~ /mswin|mingw/)
|
||||
if IS_WINDOWS
|
||||
OLDAPP_NAME = "oldapp.exe"
|
||||
NEWAPP_NAME = "newapp.exe"
|
||||
APP_NAME = "app.exe"
|
||||
UPDATER_NAME = "updater.exe"
|
||||
ZIP_TOOL = "C:/Cygwin/bin/zip.exe"
|
||||
ZIP_TOOL = File.expand_path("../zip-tool.exe")
|
||||
else
|
||||
OLDAPP_NAME = "oldapp"
|
||||
NEWAPP_NAME = "newapp"
|
||||
APP_NAME = "app"
|
||||
UPDATER_NAME = "updater"
|
||||
ZIP_TOOL = "zip"
|
||||
ZIP_TOOL = File.expand_path("../zip-tool")
|
||||
end
|
||||
|
||||
file_list_vars = {
|
||||
|
@ -39,11 +42,67 @@ def replace_vars(src_file,dest_file,vars)
|
|||
end
|
||||
end
|
||||
|
||||
def zip_supports_bzip2(zip_tool)
|
||||
# Try making an empty zip file with bzip2 compression, if bzip2 is not
|
||||
# supported, the tool will output an error, otherwise it will output
|
||||
# "Nothing to do"
|
||||
return `#{zip_tool} -Z bzip2 testing-bzip2-support.zip`.strip.include?("Nothing to do")
|
||||
# Returns true if |src_file| and |dest_file| have the same contents, type
|
||||
# and permissions or false otherwise
|
||||
def compare_files(src_file, dest_file)
|
||||
if File.ftype(src_file) != File.ftype(dest_file)
|
||||
$stderr.puts "Type of file #{src_file} and #{dest_file} differ"
|
||||
return false
|
||||
end
|
||||
|
||||
if File.file?(src_file) && !FileUtils.identical?(src_file, dest_file)
|
||||
$stderr.puts "Contents of file #{src_file} and #{dest_file} differ"
|
||||
return false
|
||||
end
|
||||
|
||||
src_stat = File.stat(src_file)
|
||||
dest_stat = File.stat(dest_file)
|
||||
|
||||
if src_stat.mode != dest_stat.mode
|
||||
$stderr.puts "Permissions of #{src_file} and #{dest_file} differ"
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Compares the contents of two directories and returns a map of (file path => change type)
|
||||
# for files and directories which differ between the two
|
||||
def compare_dirs(src_dir, dest_dir)
|
||||
src_dir += '/' if !src_dir.end_with?('/')
|
||||
dest_dir += '/' if !dest_dir.end_with?('/')
|
||||
|
||||
src_file_map = {}
|
||||
Find.find(src_dir) do |src_file|
|
||||
src_file = src_file[src_dir.length..-1]
|
||||
src_file_map[src_file] = nil
|
||||
end
|
||||
|
||||
change_map = {}
|
||||
Find.find(dest_dir) do |dest_file|
|
||||
dest_file = dest_file[dest_dir.length..-1]
|
||||
|
||||
if !src_file_map.include?(dest_file)
|
||||
change_map[dest_file] = :deleted
|
||||
elsif !compare_files("#{src_dir}/#{dest_file}", "#{dest_dir}/#{dest_file}")
|
||||
change_map[dest_file] = :updated
|
||||
end
|
||||
|
||||
src_file_map.delete(dest_file)
|
||||
end
|
||||
|
||||
src_file_map.each do |file|
|
||||
change_map[file] = :added
|
||||
end
|
||||
|
||||
return change_map
|
||||
end
|
||||
|
||||
def create_test_file(name, content)
|
||||
File.open(name, 'w') do |file|
|
||||
file.puts content
|
||||
end
|
||||
return name
|
||||
end
|
||||
|
||||
force_elevation = false
|
||||
|
@ -58,44 +117,62 @@ OptionParser.new do |parser|
|
|||
end
|
||||
end.parse!
|
||||
|
||||
BZIP2_AVAILABLE = zip_supports_bzip2(ZIP_TOOL)
|
||||
if (BZIP2_AVAILABLE)
|
||||
ZIP_FLAGS = "-Z bzip2"
|
||||
else
|
||||
ZIP_FLAGS = ""
|
||||
end
|
||||
|
||||
if (BZIP2_AVAILABLE)
|
||||
puts "Using bzip2 compression"
|
||||
else
|
||||
puts "Using plain old deflate compression - the 'zip' tool does not support bzip2"
|
||||
# copy 'src' to 'dest', preserving the attributes
|
||||
# of 'src'
|
||||
def copy_file(src, dest)
|
||||
FileUtils.cp src, dest, :preserve => true
|
||||
end
|
||||
|
||||
# Remove the install and package dirs if they
|
||||
# already exist
|
||||
FileUtils.rm_rf(INSTALL_DIR)
|
||||
FileUtils.rm_rf(PACKAGE_DIR)
|
||||
FileUtils.rm_rf(PACKAGE_SRC_DIR)
|
||||
|
||||
# Create the install directory with the old app
|
||||
Dir.mkdir(INSTALL_DIR)
|
||||
FileUtils.cp(OLDAPP_NAME,"#{INSTALL_DIR}/#{APP_NAME}")
|
||||
copy_file OLDAPP_NAME, "#{INSTALL_DIR}/#{APP_NAME}"
|
||||
|
||||
# Create a dummy file to uninstall
|
||||
uninstall_test_file = "#{INSTALL_DIR}/file-to-uninstall.txt"
|
||||
File.open(uninstall_test_file,"w") do |file|
|
||||
file.puts "this file should be removed after the update"
|
||||
uninstall_test_file = create_test_file("#{INSTALL_DIR}/file-to-uninstall.txt", "this file should be removed after the update")
|
||||
uninstall_test_symlink = if not IS_WINDOWS
|
||||
FileUtils.ln_s("#{INSTALL_DIR}/file-to-uninstall.txt", "#{INSTALL_DIR}/symlink-to-file-to-uninstall.txt")
|
||||
else
|
||||
create_test_file("#{INSTALL_DIR}/symlink-to-file-to-uninstall.txt", "dummy file. this is a symlink on Unix")
|
||||
end
|
||||
|
||||
# Create the update archive containing the new app
|
||||
# Create a dummy file to uninstall in a directory
|
||||
# which becomes empty after the update
|
||||
empty_dir_path = "#{INSTALL_DIR}/will-become-empty-after-update/nested"
|
||||
FileUtils.mkdir_p(empty_dir_path)
|
||||
create_test_file("#{empty_dir_path}/file-to-uninstall.txt", "this file and its containing dir should be removed after the update")
|
||||
|
||||
# Populate package source dir with files to install
|
||||
Dir.mkdir(PACKAGE_SRC_DIR)
|
||||
nested_dir_path = "#{PACKAGE_SRC_DIR}/new-dir/new-dir2"
|
||||
FileUtils.mkdir_p(nested_dir_path)
|
||||
FileUtils::chmod 0755, "#{PACKAGE_SRC_DIR}/new-dir"
|
||||
FileUtils::chmod 0755, "#{PACKAGE_SRC_DIR}/new-dir/new-dir2"
|
||||
nested_dir_test_file = "#{nested_dir_path}/new-file.txt"
|
||||
File.open(nested_dir_test_file,'w') do |file|
|
||||
file.puts "this is a new file in a new nested dir"
|
||||
end
|
||||
FileUtils::chmod 0644, nested_dir_test_file
|
||||
copy_file NEWAPP_NAME, "#{PACKAGE_SRC_DIR}/#{APP_NAME}"
|
||||
FileUtils::chmod 0755, "#{PACKAGE_SRC_DIR}/#{APP_NAME}"
|
||||
|
||||
# Create .zip packages from source files
|
||||
Dir.mkdir(PACKAGE_DIR)
|
||||
FileUtils.cp(NEWAPP_NAME,"#{PACKAGE_DIR}/#{APP_NAME}")
|
||||
system("#{ZIP_TOOL} #{ZIP_FLAGS} #{PACKAGE_DIR}/app-pkg.zip -j #{PACKAGE_DIR}/#{APP_NAME}")
|
||||
FileUtils.rm("#{PACKAGE_DIR}/#{APP_NAME}")
|
||||
Dir.chdir(PACKAGE_SRC_DIR) do
|
||||
if !system("#{ZIP_TOOL} #{PACKAGE_DIR}/app-pkg.zip .")
|
||||
raise "Unable to create update package"
|
||||
end
|
||||
end
|
||||
|
||||
# Copy the install script and updater to the target
|
||||
# directory
|
||||
replace_vars("file_list.xml","#{PACKAGE_DIR}/file_list.xml",file_list_vars)
|
||||
FileUtils.cp("../#{UPDATER_NAME}","#{PACKAGE_DIR}/#{UPDATER_NAME}")
|
||||
copy_file "../#{UPDATER_NAME}", "#{PACKAGE_DIR}/#{UPDATER_NAME}"
|
||||
|
||||
# Run the updater using the new syntax
|
||||
#
|
||||
|
@ -107,7 +184,7 @@ install_path = File.expand_path(INSTALL_DIR)
|
|||
Dir.chdir(INSTALL_DIR) do
|
||||
flags = "--force-elevated" if force_elevation
|
||||
debug_flags = "gdb --args" if run_in_debugger
|
||||
cmd = "#{debug_flags} #{PACKAGE_DIR}/#{UPDATER_NAME} #{flags} --install-dir \"#{install_path}\" --package-dir \"#{PACKAGE_DIR}\" --script file_list.xml"
|
||||
cmd = "#{debug_flags} #{PACKAGE_DIR}/#{UPDATER_NAME} #{flags} --install-dir \"#{install_path}\" --package-dir \"#{PACKAGE_DIR}\" --script file_list.xml --auto-close"
|
||||
puts "Running '#{cmd}'"
|
||||
system(cmd)
|
||||
end
|
||||
|
@ -122,13 +199,26 @@ if (output.strip != "new app starting")
|
|||
throw "Updated app produced unexpected output: #{output}"
|
||||
end
|
||||
|
||||
# Check that the permissions were correctly set on the installed app
|
||||
mode = File.stat(app_path).mode.to_s(8)
|
||||
if (mode != "100755")
|
||||
throw "Updated app has incorrect permissions: #{mode}"
|
||||
# Check that the packaged dir and install dir match
|
||||
dir_diff = compare_dirs(PACKAGE_SRC_DIR, INSTALL_DIR)
|
||||
ignored_files = ["test-dir", "test-dir/app-symlink", UPDATER_NAME]
|
||||
have_unexpected_change = false
|
||||
dir_diff.each do |path, change_type|
|
||||
if !ignored_files.include?(path)
|
||||
case change_type
|
||||
when :added
|
||||
$stderr.puts "File #{path} was not installed"
|
||||
when :changed
|
||||
$stderr.puts "File #{path} differs between install and package dir"
|
||||
when :deleted
|
||||
$stderr.puts "File #{path} was not uninstalled"
|
||||
end
|
||||
have_unexpected_change = true
|
||||
end
|
||||
end
|
||||
|
||||
if (File.exist?(uninstall_test_file))
|
||||
throw "File to uninstall was not removed"
|
||||
if have_unexpected_change
|
||||
throw "Unexpected differences between packaging and update dir"
|
||||
end
|
||||
|
||||
puts "Test passed"
|
||||
|
|
|
@ -46,15 +46,23 @@
|
|||
<size>$UPDATER_SIZE</size>
|
||||
<permissions>0755</permissions>
|
||||
</file>
|
||||
|
||||
<!-- Test symlink !-->
|
||||
<file>
|
||||
<name>test-dir/app-symlink</name>
|
||||
<target>../app</target>
|
||||
</file>
|
||||
<!-- Test file in new directory !-->
|
||||
<file>
|
||||
<name>new-dir/new-dir2/new-file.txt</name>
|
||||
<hash>$TEST_FILENAME</hash>
|
||||
<size>$TEST_SIZE</size>
|
||||
<package>app-pkg</package>
|
||||
<permissions>0644</permissions>
|
||||
</file>
|
||||
</install-v3>
|
||||
<uninstall>
|
||||
<!-- TODO - List some files to uninstall here !-->
|
||||
<file>file-to-uninstall.txt</file>
|
||||
<file>symlink-to-file-to-uninstall.txt</file>
|
||||
<file>will-become-empty-after-update/nested/file-to-uninstall.txt</file>
|
||||
</uninstall>
|
||||
</update>
|
||||
|
|
71
src/zip-tool.cpp
Normal file
71
src/zip-tool.cpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include "DirIterator.h"
|
||||
#include "Log.h"
|
||||
#include "FileUtils.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
// Simple utility for creating zip files from the
|
||||
// contents of a directory
|
||||
//
|
||||
// The advantage of this over the 'zip' tool on Linux/Mac is consistent
|
||||
// behavior across platforms and support for Windows.
|
||||
//
|
||||
// Usage: zip-tool <archive name> <dir>
|
||||
|
||||
// scan a directory and record paths to files that are found
|
||||
void scanDir(std::vector<std::string>& filesFound, const std::string& path)
|
||||
{
|
||||
DirIterator iter(path.c_str());
|
||||
while (iter.next())
|
||||
{
|
||||
if (iter.isDir())
|
||||
{
|
||||
if (iter.fileName() == "." || iter.fileName() == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
scanDir(filesFound, iter.filePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
filesFound.push_back(iter.filePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0] << " <archive name> <input dir>" << std::endl << std::endl
|
||||
<< "Recursively scan <input dir> and add all files found to the ZIP archive <archive name>"
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string archivePath(argv[1]);
|
||||
std::string inputDir(argv[2]);
|
||||
|
||||
try
|
||||
{
|
||||
std::vector<std::string> paths;
|
||||
scanDir(paths, inputDir);
|
||||
for (std::vector<std::string>::const_iterator iter = paths.begin();
|
||||
iter != paths.end();
|
||||
++iter)
|
||||
{
|
||||
std::string path = iter->substr(inputDir.size()+1);
|
||||
std::string content = FileUtils::readFile(iter->c_str());
|
||||
LOG(Info, "Adding " + path + " to archive " + archivePath);
|
||||
FileUtils::addToZip(archivePath.c_str(), path.c_str(), content.data(), static_cast<int>(content.length()));
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
std::cerr << "Creating zip file failed: " << ex.what() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/ruby
|
||||
|
||||
require 'digest/sha1'
|
||||
require 'fileutils'
|
||||
require 'rubygems'
|
||||
require 'find'
|
||||
|
@ -63,7 +64,7 @@ def hash_to_xml(root,map)
|
|||
map.each do |key,value|
|
||||
element = REXML::Element.new(key)
|
||||
if value.instance_of?(String)
|
||||
element.text = value
|
||||
element.text = value.dup
|
||||
elsif value.instance_of?(Hash)
|
||||
hash_to_xml(element,value)
|
||||
elsif !value.nil?
|
||||
|
@ -82,7 +83,7 @@ def strip_prefix(string,prefix)
|
|||
end
|
||||
|
||||
def file_sha1(path)
|
||||
return `sha1sum "#{path}"`.split(' ')[0]
|
||||
Digest::SHA1.file(path).to_s
|
||||
end
|
||||
|
||||
class UpdateScriptGenerator
|
||||
|
@ -180,12 +181,10 @@ class UpdateScriptGenerator
|
|||
|
||||
def deps_to_xml()
|
||||
deps_elem = REXML::Element.new("dependencies")
|
||||
deps = @config.updater_binary
|
||||
deps.each do |dependency|
|
||||
dep_elem = REXML::Element.new("file")
|
||||
dep_elem.text = dependency
|
||||
deps_elem.add_element dep_elem
|
||||
end
|
||||
dependency = @config.updater_binary
|
||||
dep_elem = REXML::Element.new("file")
|
||||
dep_elem.text = dependency
|
||||
deps_elem.add_element dep_elem
|
||||
return deps_elem
|
||||
end
|
||||
|
||||
|
@ -387,7 +386,7 @@ if !updater_binary_input_path
|
|||
exit(1)
|
||||
end
|
||||
|
||||
FileUtils.cp(updater_binary_input_path,"#{output_dir}/#{File.basename(updater_binary_input_path)}")
|
||||
FileUtils.cp updater_binary_input_path, "#{output_dir}/#{File.basename(updater_binary_input_path)}", :preserve => true
|
||||
|
||||
# output the file_list.xml file
|
||||
update_script = UpdateScriptGenerator.new(target_version,target_platform,input_dir,
|
||||
|
|
Loading…
Reference in a new issue