Compare commits

..

No commits in common. "master" and "0.10" have entirely different histories.
master ... 0.10

49 changed files with 488 additions and 850 deletions

2
.gitignore vendored
View file

@ -1,2 +0,0 @@
build/
project/

View file

@ -1,40 +1,28 @@
project(updater)
cmake_minimum_required(VERSION 2.6)
enable_testing()
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
set(CMAKE_MODULE_PATH "${CMAKE_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.
#
# 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")
# 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)
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.
# - Enable PDB generation for release builds
set(CMAKE_CXX_FLAGS_DEBUG "/MT")
set(CMAKE_C_FLAGS_DEBUG "/MT")
# 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")
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")
@ -47,12 +35,14 @@ if (APPLE)
# of the updater binary
set(CMAKE_OSX_ARCHITECTURES i386;x86_64)
# 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}")
# 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")
endif()
add_subdirectory(src)

View file

@ -17,35 +17,44 @@ 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
@ -55,40 +64,45 @@ Once the updater has been started, it:
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),
@ -96,7 +110,8 @@ Once the updater has been started, it:
* 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
@ -116,23 +131,23 @@ Once the updater has been started, it:
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 Normal file
View file

@ -0,0 +1,72 @@
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

Binary file not shown.

View file

@ -18,7 +18,7 @@ set (HEADERS
zip.h
)
if (APPLE OR ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
if (APPLE)
# Mac OS X does not have fopen64()
# and several related functions as the standard fopen()
# calls are 64bit
@ -37,17 +37,15 @@ if (UNIX)
# 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")
set(BZ2_LIB_NAME bz2)
if (NOT APPLE)
set(BZ2_LIB_NAME bz2.a)
endif()
target_link_libraries(minizip z ${BZ2_LIB_NAME})
else()
target_link_libraries(minizip
"${CMAKE_CURRENT_SOURCE_DIR}/../zlib/prebuilt/zlib_static.lib"

View file

@ -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 "
"ET:Legacy from http://www.etlegacy.com/";
"Mendeley Desktop from http://www.mendeley.com/download-mendeley-desktop";
return result;
}

View file

@ -24,16 +24,16 @@ class AppInfo
inline std::string AppInfo::name()
{
return "Legacy Updater";
return "Mendeley Updater";
}
inline std::string AppInfo::appName()
{
return "ET:Legacy";
return "Mendeley Desktop";
}
inline std::string AppInfo::organizationName()
{
return "Legacy Team.";
return "Mendeley Ltd.";
}

View file

@ -8,15 +8,18 @@ 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)
find_package(GTK2 REQUIRED gtk)
include_directories(${GTK2_INCLUDE_DIRS})
include_directories(${GTK2_INCLUDE_DIR})
add_definitions(${GTK2_CFLAGS})
add_library(updatergtk SHARED UpdateDialogGtk.cpp UpdateDialogGtk.h)
target_link_libraries(updatergtk ${GTK2_LIBRARIES})
target_link_libraries(updatergtk ${GTK2_LINK_FLAGS})
endif()
add_definitions(-DTIXML_USE_STL)
@ -28,7 +31,6 @@ set (SOURCES
Log.cpp
ProcessUtils.cpp
StandardDirs.cpp
UpdateDialog.cpp
UpdateInstaller.cpp
UpdateScript.cpp
UpdaterOptions.cpp
@ -82,8 +84,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} UpdateDialogGtkFactory.cpp ${GTK_BIN_CPP_FILE})
set(HEADERS ${HEADERS} UpdateDialogGtkFactory.h)
set(SOURCES ${SOURCES} UpdateDialogGtkWrapper.cpp ${GTK_BIN_CPP_FILE})
set(HEADERS ${HEADERS} UpdateDialogGtkWrapper.h)
endif()
if (APPLE)
@ -105,21 +107,8 @@ 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)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_link_libraries(updatershared dl)
endif()
target_link_libraries(updatershared pthread dl)
endif()
if (WIN32)
@ -128,15 +117,20 @@ 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 ${BINARY_SIGNING_TOOL} $<TARGET_FILE:updater>)
add_custom_command(TARGET updater POST_BUILD COMMAND sign-updater.bat updater.exe)
endif()
add_executable(zip-tool zip-tool.cpp)
target_link_libraries(zip-tool updatershared)
install(TARGETS updater RUNTIME DESTINATION bin)

View file

@ -11,7 +11,6 @@
#include <fstream>
#include <iostream>
#include "minizip/zip.h"
#include "minizip/unzip.h"
#ifdef PLATFORM_UNIX
@ -81,7 +80,7 @@ bool FileUtils::fileExists(const char* path) throw (IOException)
{
#ifdef PLATFORM_UNIX
struct stat fileInfo;
if (lstat(path,&fileInfo) != 0)
if (stat(path,&fileInfo) != 0)
{
if (errno == ENOENT)
{
@ -121,7 +120,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,static_cast<mode_t>(mode)) != 0)
if (::chmod(path,mode) != 0)
{
throw IOException("Failed to set permissions on " + std::string(path) + " to " + intToStr(mode));
}
@ -146,37 +145,6 @@ 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);
@ -482,12 +450,7 @@ std::string FileUtils::toUnixPathSeparators(const std::string& str)
std::string FileUtils::tempPath()
{
#ifdef PLATFORM_UNIX
std::string tmpDir(notNullString(getenv("TMPDIR")));
if (tmpDir.empty())
{
tmpDir = "/tmp";
}
return tmpDir;
return "/tmp";
#else
char buffer[MAX_PATH+1];
GetTempPath(MAX_PATH+1,buffer);
@ -498,7 +461,7 @@ std::string FileUtils::tempPath()
bool startsWithDriveLetter(const char* path)
{
return strlen(path) >= 2 &&
(isalpha(path[0])) &&
(path[0] >= 'A' && path[0] <= 'Z') &&
path[1] == ':';
}
@ -536,17 +499,6 @@ 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
@ -629,41 +581,3 @@ 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;
}
}

View file

@ -67,17 +67,8 @@ 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);
@ -109,9 +100,6 @@ 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.
*/
@ -138,19 +126,10 @@ 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);
};

View file

@ -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(),static_cast<int>(infoPlist.size()));
FileUtils::writeFile((contentDir + "/Info.plist").c_str(),infoPlist.c_str(),infoPlist.size());
// save the icon to Contents/Resources/<appname>.icns
FileUtils::writeFile((resourceDir + '/' + m_appName + ".icns").c_str(),icon.c_str(),static_cast<int>(icon.size()));
FileUtils::writeFile((resourceDir + '/' + m_appName + ".icns").c_str(),icon.c_str(),icon.size());
// copy the app binary to Contents/MacOS/<appname>
m_exePath = binDir + '/' + m_appName;

View file

@ -5,10 +5,6 @@
#define PLATFORM_LINUX
#endif
#ifdef __FreeBSD__
#define PLATFORM_FREEBSD
#endif
#ifdef WIN32
#define PLATFORM_WINDOWS
#include <windows.h>
@ -22,14 +18,13 @@
#define PLATFORM_MAC
#endif
#if defined(PLATFORM_LINUX) || defined(PLATFORM_MAC) || defined(PLATFORM_FREEBSD)
#if defined(PLATFORM_LINUX) || defined(PLATFORM_MAC)
#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

View file

@ -17,11 +17,6 @@
#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>
@ -92,7 +87,7 @@ int ProcessUtils::runElevated(const std::string& executable,
#elif defined(PLATFORM_MAC)
(void)task;
return runElevatedMac(executable,args);
#elif defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#elif defined(PLATFORM_LINUX)
return runElevatedLinux(executable,args,task);
#endif
}
@ -127,7 +122,7 @@ bool ProcessUtils::waitForProcess(PLATFORM_PID pid)
#endif
}
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#ifdef PLATFORM_LINUX
int ProcessUtils::runElevatedLinux(const std::string& executable,
const std::list<std::string>& args,
const std::string& _task)
@ -201,11 +196,6 @@ 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)
{
@ -320,7 +310,6 @@ int ProcessUtils::runElevatedMac(const std::string& executable,
return RunElevatedFailed;
}
}
#pragma clang diagnostic pop
#endif
// convert a list of arguments in a space-separated string.
@ -488,23 +477,7 @@ int ProcessUtils::runWindows(const std::string& _executable,
std::string ProcessUtils::currentProcessPath()
{
#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)
#ifdef PLATFORM_LINUX
std::string path = FileUtils::canonicalPath("/proc/self/exe");
LOG(Info,"Current process path " + path);
return path;

View file

@ -6,7 +6,6 @@
#ifdef PLATFORM_UNIX
#include <stdlib.h>
#include <pwd.h>
#include <unistd.h>
#endif
#ifdef PLATFORM_WINDOWS
@ -34,7 +33,7 @@ std::string StandardDirs::homeDir()
std::string StandardDirs::appDataPath(const std::string& organizationName,
const std::string& appName)
{
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#ifdef PLATFORM_LINUX
std::string xdgDataHome = notNullString(getenv("XDG_DATA_HOME"));
if (xdgDataHome.empty())
{

View file

@ -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) != std::string::npos;
return str.find(text,str.size() - length) != 0;
}
inline bool startsWith(const std::string& str, const char* text)

View file

@ -1,25 +0,0 @@
#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();
}
}

View file

@ -1,29 +0,0 @@
#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;
};

View file

@ -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(int /* argc */, char** /* argv */)
void UpdateDialogAscii::init()
{
const char* path = "/tmp/update-progress";
m_output.open(path);
@ -56,15 +56,6 @@ 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()
{
}

View file

@ -1,6 +1,6 @@
#pragma once
#include "UpdateDialog.h"
#include "UpdateObserver.h"
#include <fstream>
#include "tinythread.h"
@ -11,15 +11,11 @@
* The 'dialog' consists of an xterm tailing the contents
* of a file, into which progress messages are written.
*/
class UpdateDialogAscii : public UpdateDialog
class UpdateDialogAscii : public UpdateObserver
{
public:
// implements UpdateDialog
virtual void init(int argc, char** argv);
virtual void exec();
virtual void quit();
void init();
// implements UpdateObserver
virtual void updateError(const std::string& errorMessage);
virtual void updateProgress(int percentage);
virtual void updateFinished();

View file

@ -1,20 +1,17 @@
#pragma once
#include "UpdateDialog.h"
#include "UpdateObserver.h"
class UpdateDialogPrivate;
class UpdateDialogCocoa : public UpdateDialog
class UpdateDialogCocoa : public UpdateObserver
{
public:
UpdateDialogCocoa();
~UpdateDialogCocoa();
// implements UpdateDialog
virtual void init(int argc, char** argv);
virtual void exec();
virtual void quit();
void init();
void exec();
// implements UpdateObserver
virtual void updateError(const std::string& errorMessage);

View file

@ -42,14 +42,18 @@ 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: @"There was a problem installing the update:\n\n%@", arg];
informativeTextWithFormat: message];
[alert runModal];
[message release];
}
- (void) reportUpdateProgress: (id)arg
{
@ -98,7 +102,7 @@ void UpdateDialogCocoa::enableDockIcon()
TransformProcessType(&psn,kProcessTransformToForegroundApplication);
}
void UpdateDialogCocoa::init(int /* argc */, char** /* argv */)
void UpdateDialogCocoa::init()
{
enableDockIcon();
@ -173,7 +177,6 @@ void UpdateDialogCocoa::updateFinished()
[d->delegate performSelectorOnMainThread:@selector(reportUpdateFinished:)
withObject:nil
waitUntilDone:false];
UpdateDialog::updateFinished();
}
void* UpdateDialogCocoa::createAutoreleasePool()
@ -186,9 +189,4 @@ void UpdateDialogCocoa::releaseAutoreleasePool(void* arg)
[(id)arg release];
}
void UpdateDialogCocoa::quit()
{
[NSApp performSelectorOnMainThread:@selector(stop:) withObject:d->delegate waitUntilDone:false];
}

View file

@ -6,9 +6,31 @@
#include <glib.h>
#include <gtk/gtk.h>
UpdateDialogGtk* update_dialog_gtk_new()
UpdateDialogGtk* update_dialog_gtk_new(int argc, char** argv)
{
return new UpdateDialogGtk();
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();
}
UpdateDialogGtk::UpdateDialogGtk()
@ -73,12 +95,6 @@ void UpdateDialogGtk::exec()
}
void UpdateDialogGtk::finish(GtkWidget* widget, gpointer _dialog)
{
UpdateDialogGtk* dialog = static_cast<UpdateDialogGtk*>(_dialog);
dialog->quit();
}
void UpdateDialogGtk::quit()
{
gtk_main_quit();
}
@ -149,7 +165,6 @@ void UpdateDialogGtk::updateFinished()
{
UpdateMessage* message = new UpdateMessage(this,UpdateMessage::UpdateFinished);
g_idle_add(&UpdateDialogGtk::notify,message);
UpdateDialog::updateFinished();
}

View file

@ -1,20 +1,17 @@
#pragma once
#include "UpdateDialog.h"
#include "UpdateMessage.h"
#include "UpdateObserver.h"
#include <gtk/gtk.h>
class UpdateDialogGtk : public UpdateDialog
class UpdateDialogGtk : public UpdateObserver
{
public:
UpdateDialogGtk();
// implements UpdateDialog
virtual void init(int argc, char** argv);
virtual void exec();
virtual void quit();
void init(int argc, char** argv);
void exec();
// observer callbacks - these may be called
// from a background thread
@ -36,7 +33,11 @@ class UpdateDialogGtk : public UpdateDialog
// 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();
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);
}

View file

@ -1,72 +0,0 @@
#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());
}

View file

@ -1,13 +0,0 @@
#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();
};

View file

@ -0,0 +1,95 @@
#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);
}

View file

@ -0,0 +1,30 @@
#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;
};

View file

@ -83,7 +83,7 @@ UpdateDialogWin32::~UpdateDialogWin32()
}
}
void UpdateDialogWin32::init(int /* argc */, char** /* argv */)
void UpdateDialogWin32::init()
{
int width = 300;
int height = 130;
@ -141,12 +141,6 @@ 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)
@ -163,7 +157,7 @@ LRESULT WINAPI UpdateDialogWin32::windowProc(HWND window, UINT message, WPARAM w
{
if (reinterpret_cast<HWND>(lParam) == m_finishButton.GetHwnd())
{
quit();
PostQuitMessage(0);
}
}
break;

View file

@ -1,23 +1,21 @@
#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 UpdateDialog
class UpdateDialogWin32 : public UpdateObserver
{
public:
UpdateDialogWin32();
~UpdateDialogWin32();
// implements UpdateDialog
virtual void init(int argc, char** argv);
virtual void exec();
virtual void quit();
void init();
void exec();
// implements UpdateObserver
virtual void updateError(const std::string& errorMessage);

View file

@ -12,7 +12,6 @@ UpdateInstaller::UpdateInstaller()
, m_script(0)
, m_observer(0)
, m_forceElevated(false)
, m_autoClose(false)
{
}
@ -60,10 +59,6 @@ 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;
}
@ -276,7 +271,7 @@ void UpdateInstaller::installFile(const UpdateScriptFile& file)
std::string destDir = FileUtils::dirname(destPath.c_str());
if (!FileUtils::fileExists(destDir.c_str()))
{
FileUtils::mkpath(destDir.c_str());
FileUtils::mkdir(destDir.c_str());
}
if (target.empty())
@ -412,24 +407,13 @@ void UpdateInstaller::restartMainApp()
std::string command;
std::list<std::string> args;
if (!m_executable.empty())
for (std::vector<UpdateScriptFile>::const_iterator iter = m_script->filesToInstall().begin();
iter != m_script->filesToInstall().end();
iter++)
{
command = m_installDir + '/' + m_executable;
if (!m_executable_args.empty())
if (iter->isMainBinary)
{
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;
}
command = m_installDir + '/' + iter->path;
}
}
@ -459,23 +443,5 @@ 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;
}

View file

@ -32,9 +32,6 @@ 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);
@ -67,8 +64,5 @@ 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;
};

View file

@ -9,8 +9,7 @@
#include <cstdlib>
#include <iostream>
#if defined(PLATFORM_WINDOWS) && (_MSC_VER < 1800)
// atoll() was added in MSVC 2013
#ifdef PLATFORM_WINDOWS
long long atoll(const char* string)
{
return _atoi64(string);
@ -22,7 +21,6 @@ UpdaterOptions::UpdaterOptions()
, waitPid(0)
, showVersion(false)
, forceElevated(false)
, autoClose(false)
{
}
@ -71,7 +69,7 @@ void UpdaterOptions::parseOldFormatArgs(int argc, char** argv)
// binary. On Mac and Linux this differs from the root of
// the installation directory
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#ifdef PLATFORM_LINUX
// the main binary is in lib/mendeleydesktop/libexec,
// go up 3 levels
installDir = FileUtils::canonicalPath((value + "/../../../").c_str());
@ -115,11 +113,8 @@ 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);
@ -143,18 +138,9 @@ 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())
{

View file

@ -14,13 +14,10 @@ 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);

View file

@ -9,8 +9,8 @@
#include "tinythread.h"
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#include "UpdateDialogGtkFactory.h"
#if defined(PLATFORM_LINUX)
#include "UpdateDialogGtkWrapper.h"
#include "UpdateDialogAscii.h"
#endif
@ -24,11 +24,10 @@
#endif
#include <iostream>
#include <memory>
#define UPDATER_VERSION "0.16"
#define UPDATER_VERSION "0.10"
UpdateDialog* createUpdateDialog();
void runWithUi(int argc, char** argv, UpdateInstaller* installer);
void runUpdaterThread(void* arg)
{
@ -145,20 +144,10 @@ 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)
{
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();
runWithUi(argc,argv,&installer);
}
else
{
@ -172,21 +161,41 @@ int main(int argc, char** argv)
return 0;
}
UpdateDialog* createUpdateDialog()
#ifdef PLATFORM_LINUX
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
{
#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)
UpdateDialogAscii asciiDialog;
UpdateDialogGtkWrapper dialog;
bool useGtk = dialog.init(argc,argv);
if (useGtk)
{
dialog = new UpdateDialogAscii();
installer->setObserver(&dialog);
}
return dialog;
#endif
else
{
asciiDialog.init();
installer->setObserver(&asciiDialog);
}
tthread::thread updaterThread(runUpdaterThread,installer);
if (useGtk)
{
dialog.exec();
}
updaterThread.join();
}
#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
@ -200,4 +209,14 @@ 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

BIN
src/resources/updater.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -1,4 +1,4 @@
IDI_APPICON ICON DISCARDABLE "etl.ico"
IDI_APPICON ICON DISCARDABLE "updater.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", "Legacy Team."
VALUE "LegalCopyright", "© 2011-2014 Legacy Team"
VALUE "ProductName", "Legacy Updater"
VALUE "CompanyName", "Mendeley Ltd."
VALUE "LegalCopyright", "(C) Mendeley Ltd. 2011"
VALUE "ProductName", "Mendeley Software Updater"
VALUE "PrivateBuild", "Built by Robert Knight"
END
END

View file

@ -24,22 +24,27 @@ foreach(TEST_FILE ${TEST_FILES})
endforeach()
# Add unit test binaries
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)
add_executable(TestUpdateScript
TestUpdateScript.cpp
)
target_link_libraries(TestUpdateScript
updatershared
)
add_executable(TestUpdaterOptions
TestUpdaterOptions.cpp
)
target_link_libraries(TestUpdaterOptions
updatershared
)
add_executable(TestFileUtils
TestFileUtils.cpp
)
target_link_libraries(TestFileUtils
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()

View file

@ -11,59 +11,9 @@ void TestFileUtils::testDirName()
#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);
}
}

View file

@ -4,8 +4,4 @@ class TestFileUtils
{
public:
void testDirName();
void testIsRelative();
void testSymlinkFileExists();
void testStandardDirs();
void testRemoveEmptyDirs();
};
};

View file

@ -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
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#ifdef PLATFORM_LINUX
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);
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#ifdef PLATFORM_LINUX
TEST_COMPARE(options.installDir,"/tmp/path-to-app");
#elif defined(PLATFORM_MAC)
// /tmp is a symlink to /private/tmp on Mac

View file

@ -105,4 +105,3 @@ inline std::string TestUtils::toString(const char* value, const char*)
#define TEST_COMPARE(x,y) \
TestUtils::compare(x,y,#x,#y);

View file

@ -35,18 +35,9 @@
<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>

View file

@ -1,7 +1,6 @@
#!/usr/bin/ruby
require 'fileutils.rb'
require 'find'
require 'rbconfig'
require 'optparse'
@ -10,21 +9,19 @@ 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 IS_WINDOWS
if (RbConfig::CONFIG['host_os'] =~ /mswin|mingw/)
OLDAPP_NAME = "oldapp.exe"
NEWAPP_NAME = "newapp.exe"
APP_NAME = "app.exe"
UPDATER_NAME = "updater.exe"
ZIP_TOOL = File.expand_path("../zip-tool.exe")
ZIP_TOOL = "C:/Cygwin/bin/zip.exe"
else
OLDAPP_NAME = "oldapp"
NEWAPP_NAME = "newapp"
APP_NAME = "app"
UPDATER_NAME = "updater"
ZIP_TOOL = File.expand_path("../zip-tool")
ZIP_TOOL = "zip"
end
file_list_vars = {
@ -42,67 +39,11 @@ def replace_vars(src_file,dest_file,vars)
end
end
# 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
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")
end
force_elevation = false
@ -117,62 +58,44 @@ OptionParser.new do |parser|
end
end.parse!
# copy 'src' to 'dest', preserving the attributes
# of 'src'
def copy_file(src, dest)
FileUtils.cp src, dest, :preserve => true
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"
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)
copy_file OLDAPP_NAME, "#{INSTALL_DIR}/#{APP_NAME}"
FileUtils.cp(OLDAPP_NAME,"#{INSTALL_DIR}/#{APP_NAME}")
# Create a dummy file to uninstall
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")
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"
end
# 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
# Create the update archive containing the new app
Dir.mkdir(PACKAGE_DIR)
Dir.chdir(PACKAGE_SRC_DIR) do
if !system("#{ZIP_TOOL} #{PACKAGE_DIR}/app-pkg.zip .")
raise "Unable to create update package"
end
end
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}")
# Copy the install script and updater to the target
# directory
replace_vars("file_list.xml","#{PACKAGE_DIR}/file_list.xml",file_list_vars)
copy_file "../#{UPDATER_NAME}", "#{PACKAGE_DIR}/#{UPDATER_NAME}"
FileUtils.cp("../#{UPDATER_NAME}","#{PACKAGE_DIR}/#{UPDATER_NAME}")
# Run the updater using the new syntax
#
@ -184,7 +107,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 --auto-close"
cmd = "#{debug_flags} #{PACKAGE_DIR}/#{UPDATER_NAME} #{flags} --install-dir \"#{install_path}\" --package-dir \"#{PACKAGE_DIR}\" --script file_list.xml"
puts "Running '#{cmd}'"
system(cmd)
end
@ -199,26 +122,13 @@ if (output.strip != "new app starting")
throw "Updated app produced unexpected output: #{output}"
end
# 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
# 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}"
end
if have_unexpected_change
throw "Unexpected differences between packaging and update dir"
if (File.exist?(uninstall_test_file))
throw "File to uninstall was not removed"
end
puts "Test passed"

View file

@ -46,23 +46,15 @@
<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>

View file

@ -1,71 +0,0 @@
#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;
}

View file

@ -1,6 +1,5 @@
#!/usr/bin/ruby
require 'digest/sha1'
require 'fileutils'
require 'rubygems'
require 'find'
@ -64,7 +63,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.dup
element.text = value
elsif value.instance_of?(Hash)
hash_to_xml(element,value)
elsif !value.nil?
@ -83,7 +82,7 @@ def strip_prefix(string,prefix)
end
def file_sha1(path)
Digest::SHA1.file(path).to_s
return `sha1sum "#{path}"`.split(' ')[0]
end
class UpdateScriptGenerator
@ -181,10 +180,12 @@ class UpdateScriptGenerator
def deps_to_xml()
deps_elem = REXML::Element.new("dependencies")
dependency = @config.updater_binary
dep_elem = REXML::Element.new("file")
dep_elem.text = dependency
deps_elem.add_element dep_elem
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
return deps_elem
end
@ -386,7 +387,7 @@ if !updater_binary_input_path
exit(1)
end
FileUtils.cp updater_binary_input_path, "#{output_dir}/#{File.basename(updater_binary_input_path)}", :preserve => true
FileUtils.cp(updater_binary_input_path,"#{output_dir}/#{File.basename(updater_binary_input_path)}")
# output the file_list.xml file
update_script = UpdateScriptGenerator.new(target_version,target_platform,input_dir,