Compare commits

..

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

58 changed files with 569 additions and 1616 deletions

2
.gitignore vendored
View file

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

View file

@ -1,44 +1,22 @@
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)
# - 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")
include_directories(external/zlib/)
include_directories(external/bzip2)
include_directories(external/win32cpp/include)
endif()
if (APPLE)
@ -47,12 +25,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)

19
LICENSE
View file

@ -1,19 +0,0 @@
This project is licensed under a BSD license.
The Mendeley Desktop icon graphics and name are trademarks of Mendeley Ltd.
and must be removed or replaced - see src/AppInfo.cpp and the files
in src/resources.
===
Copyright (c) 2011, Mendeley Ltd.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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
@ -53,42 +62,45 @@ Once the updater has been started, it:
relevant packages, file_list.xml file and updater binary to a temporary
directory and invoke the updater.
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 +108,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 +129,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.

69
TODO Normal file
View file

@ -0,0 +1,69 @@
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
* 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 (N/A? - No icon in window decorations on Mac)
* 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
* 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
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)
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
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

View file

@ -1,36 +0,0 @@
This project includes a tool for installing updates and specifies an XML-based
file format for describing the contents of a release. It does not include
the client-side tools to detect the availability of updates, download
updates that are found and invoke the update installer. It also does
not include the relevant server-side components. You will need to write
tools to do this.
The simplest option is to create, for each platform, a file_list.xml file and .zip
file containing the complete contents of the application on that platform.
1. Modify the file_list.xml file generated by the create_packages.rb script
to include <source> URLs specifying where to download the packages from and
then upload this modified file_list.xml file plus the compressed .zip packages to a server.
2. The client application should then periodically fetch the file_list.xml for the latest release
from a fixed URL (eg. http://www.yourdomain.com/updates/$PLATFORM/file_list.xml)
and check whether it is newer than the installed version, by checking the <targetVersion> element in the file.
3. If a newer version is available, the client should download the updater and .zip packages to a temporary directory
and invoke the updater to install the new version.
A more sophisticated option is to store the file_list.xml file and packages for each release
on the server. When the client checks for an update:
1. On the server, determine whether a newer version is available and if so,
determine the target version for the update. If you want to have multiple 'channels'
of updates (eg. stable channel, beta channel, development channel) then you can change
the target version depending on which channel the user is in.
2. Parse the file_list.xml for the client's current version and the
target version and generate a delta file_list.xml file listing only the
packages and files that have changed.
3. Return the delta file_list.xml file to the client, which then downloads the necessary
packages and installs the updates.

Binary file not shown.

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
@ -33,25 +33,11 @@ add_library(minizip
)
if (UNIX)
# 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()
target_link_libraries(minizip z bz2)
else()
target_link_libraries(minizip
"${CMAKE_CURRENT_SOURCE_DIR}/../zlib/prebuilt/zlib_static.lib"
"${CMAKE_CURRENT_SOURCE_DIR}/../bzip2/libbz2_static.lib"
"${CMAKE_CURRENT_SOURCE_DIR}/../zlib/prebuilt/zlib.lib"
"${CMAKE_CURRENT_SOURCE_DIR}/../bzip2/libbz2.lib"
)
endif()

Binary file not shown.

Binary file not shown.

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
@ -36,20 +38,10 @@ set (SOURCES
if (APPLE)
set(MAC_DOCK_ICON_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_dock_icon.cpp)
set(MAC_INFO_PLIST_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_info_plist.cpp)
generate_cpp_resource_file(resource_macdockicon
${CMAKE_CURRENT_SOURCE_DIR}/resources
mac.icns ${MAC_DOCK_ICON_CPP_FILE})
generate_cpp_resource_file(resource_macplist
${CMAKE_CURRENT_SOURCE_DIR}/resources
Info.plist ${MAC_INFO_PLIST_FILE})
set(HEADERS ${HEADERS} MacBundle.h)
set(SOURCES ${SOURCES}
MacBundle.cpp
StandardDirs.mm
UpdateDialogCocoa.mm
mac_dock_icon.cpp
mac_info_plist.cpp)
mac-dock.png ${MAC_DOCK_ICON_CPP_FILE})
set(SOURCES ${SOURCES} StandardDirs.mm UpdateDialogCocoa.mm mac_dock_icon.cpp)
endif()
if (WIN32)
@ -82,8 +74,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 +97,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 +107,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

@ -5,13 +5,11 @@
#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
@ -26,30 +24,20 @@
#endif
FileUtils::IOException::IOException(const std::string& error)
{
init(errno,error);
}
FileUtils::IOException::IOException(int errorCode, const std::string& error)
{
init(errorCode,error);
}
void FileUtils::IOException::init(int errorCode, const std::string& error)
: m_errno(0)
{
m_error = error;
#ifdef PLATFORM_UNIX
m_errorCode = errorCode;
m_errno = errno;
if (m_errorCode > 0)
if (m_errno > 0)
{
m_error += " details: " + std::string(strerror(m_errorCode));
m_error += " details: " + std::string(strerror(m_errno));
}
#endif
#ifdef PLATFORM_WINDOWS
m_errorCode = 0;
m_error += " GetLastError returned: " + intToStr(GetLastError());
#endif
}
@ -58,30 +46,11 @@ FileUtils::IOException::~IOException() throw ()
{
}
FileUtils::IOException::Type FileUtils::IOException::type() const
{
#ifdef PLATFORM_UNIX
switch (m_errorCode)
{
case 0:
return NoError;
case EROFS:
return ReadOnlyFileSystem;
case ENOSPC:
return DiskFull;
default:
return Unknown;
}
#else
return Unknown;
#endif
}
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)
{
@ -103,25 +72,10 @@ bool FileUtils::fileExists(const char* path) throw (IOException)
#endif
}
int FileUtils::fileMode(const char* path) throw (IOException)
{
#ifdef PLATFORM_UNIX
struct stat fileInfo;
if (stat(path,&fileInfo) != 0)
{
throw IOException("Error reading file permissions for " + std::string(path));
}
return fileInfo.st_mode;
#else
// not implemented for Windows
return 0;
#endif
}
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 +100,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);
@ -331,16 +254,7 @@ std::string FileUtils::fileName(const char* path)
#else
char baseName[MAX_PATH];
char extension[MAX_PATH];
_splitpath_s(path,
0, /* drive */
0, /* drive length */
0, /* dir */
0, /* dir length */
baseName,
MAX_PATH, /* baseName length */
extension,
MAX_PATH /* extension length */
);
_splitpath(path, 0 /* drive */, 0 /* dir */, baseName, extension);
return std::string(baseName) + std::string(extension);
#endif
}
@ -353,28 +267,9 @@ std::string FileUtils::dirname(const char* path)
free(pathCopy);
return dirname;
#else
char drive[3];
char dir[MAX_PATH];
_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;
_splitpath(path, 0 /* drive */, dir, 0 /* filename */, 0/* extension */);
return std::string(dir);
#endif
}
@ -465,29 +360,23 @@ 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;
std::replace(result.begin(),result.end(),'\\','/');
for (size_t i=0; i < result.size(); i++)
{
if (result[i] == '\\')
{
result[i] = '/';
}
}
return result;
}
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 +387,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] == ':';
}
@ -530,58 +419,6 @@ bool FileUtils::isRelative(const char* path)
#endif
}
void FileUtils::writeFile(const char* path, const char* data, int length) throw (IOException)
{
std::ofstream stream(path,std::ios::binary | std::ios::trunc);
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
std::ifstream inputFile(src,std::ios::binary);
std::ofstream outputFile(dest,std::ios::binary | std::ios::trunc);
if (!inputFile.good())
{
throw IOException("Failed to read file " + std::string(src));
}
if (!outputFile.good())
{
throw IOException("Failed to write file " + std::string(dest));
}
outputFile << inputFile.rdbuf();
if (inputFile.bad())
{
throw IOException("Error reading file " + std::string(src));
}
if (outputFile.bad())
{
throw IOException("Error writing file " + std::string(dest));
}
chmod(dest,fileMode(src));
#else
if (!CopyFile(src,dest,FALSE))
{
throw IOException("Failed to copy " + std::string(src) + " to " + std::string(dest));
}
#endif
}
std::string FileUtils::makeAbsolute(const char* path, const char* basePath)
{
if (isRelative(path))
@ -594,76 +431,3 @@ std::string FileUtils::makeAbsolute(const char* path, const char* basePath)
return path;
}
}
void FileUtils::chdir(const char* path) throw (IOException)
{
#ifdef PLATFORM_UNIX
if (::chdir(path) != 0)
{
throw FileUtils::IOException("Unable to change directory");
}
#else
if (!SetCurrentDirectory(path))
{
throw FileUtils::IOException("Unable to change directory");
}
#endif
}
std::string FileUtils::getcwd() throw (IOException)
{
#ifdef PLATFORM_UNIX
char path[PATH_MAX];
if (!::getcwd(path,PATH_MAX))
{
throw FileUtils::IOException("Failed to get current directory");
}
return std::string(path);
#else
char path[MAX_PATH];
if (GetCurrentDirectory(MAX_PATH,path) == 0)
{
throw FileUtils::IOException("Failed to get current directory");
}
return toUnixPathSeparators(std::string(path));
#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

@ -3,10 +3,8 @@
#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.
*
@ -23,33 +21,17 @@ class FileUtils
{
public:
IOException(const std::string& error);
IOException(int errorCode, const std::string& error);
virtual ~IOException() throw ();
enum Type
{
NoError,
/** Unknown error type. Call what() to get the description
* provided by the OS.
*/
Unknown,
ReadOnlyFileSystem,
DiskFull
};
virtual const char* what() const throw ()
{
return m_error.c_str();
}
Type type() const;
private:
void init(int errorCode, const std::string& error);
std::string m_error;
int m_errorCode;
int m_errno;
};
/** Remove a file. Throws an exception if the file
@ -67,23 +49,12 @@ 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);
static void createSymLink(const char* link, const char* target) throw (IOException);
static void touch(const char* path) throw (IOException);
static void copyFile(const char* src, const char* dest) throw (IOException);
/** Create all the directories in @p path which do not yet exist.
* @p path may be relative or absolute.
@ -93,9 +64,7 @@ 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.
* On Windows this includes the drive letter, if present in @p path.
*/
/** Returns the directory part of a file path. */
static std::string dirname(const char* path);
/** Remove a directory and all of its contents. */
@ -109,9 +78,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.
*/
@ -122,8 +88,6 @@ 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.
*/
@ -135,22 +99,5 @@ class FileUtils
* @p basePath should be absolute.
*/
static std::string makeAbsolute(const char* path, const char* basePath);
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

@ -1,53 +0,0 @@
#include "MacBundle.h"
#include "FileUtils.h"
#include "Log.h"
MacBundle::MacBundle(const std::string& path, const std::string& appName)
: m_appName(appName)
{
m_path = path + '/' + appName + ".app";
}
std::string MacBundle::bundlePath() const
{
return m_path;
}
void MacBundle::create(const std::string& infoPlist,
const std::string& icon,
const std::string& exePath)
{
try
{
// create the bundle directories
FileUtils::mkpath(m_path.c_str());
std::string contentDir = m_path + "/Contents";
std::string resourceDir = contentDir + "/Resources";
std::string binDir = contentDir + "/MacOS";
FileUtils::mkpath(resourceDir.c_str());
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()));
// save the icon to Contents/Resources/<appname>.icns
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;
FileUtils::copyFile(exePath.c_str(),m_exePath.c_str());
}
catch (const FileUtils::IOException& exception)
{
LOG(Error,"Unable to create app bundle. " + std::string(exception.what()));
}
}
std::string MacBundle::executablePath() const
{
return m_exePath;
}

View file

@ -1,35 +0,0 @@
#pragma once
#include <string>
/** Class for creating minimal Mac app bundles. */
class MacBundle
{
public:
/** Create a MacBundle instance representing the bundle
* in <path>/<appName>.app
*/
MacBundle(const std::string& path, const std::string& appName);
/** Create a simple Mac bundle.
*
* @param infoPlist The content of the Info.plist file
* @param icon The content of the app icon
* @param exePath The path of the file to use for the main app in the bundle.
*/
void create(const std::string& infoPlist,
const std::string& icon,
const std::string& exePath);
/** Returns the path of the main executable within the Mac bundle. */
std::string executablePath() const;
/** Returns the path of the bundle */
std::string bundlePath() const;
private:
std::string m_path;
std::string m_appName;
std::string m_exePath;
};

View file

@ -5,31 +5,22 @@
#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) || 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>
@ -37,7 +32,7 @@ PLATFORM_PID ProcessUtils::currentProcessId()
}
int ProcessUtils::runSync(const std::string& executable,
const std::list<std::string>& args)
const std::list<std::string>& args)
{
#ifdef PLATFORM_UNIX
return runSyncUnix(executable,args);
@ -52,23 +47,8 @@ int ProcessUtils::runSyncUnix(const std::string& executable,
{
PLATFORM_PID pid = runAsyncUnix(executable,args);
int status = 0;
if (waitpid(pid,&status,0) != -1)
{
if (WIFEXITED(status))
{
return static_cast<char>(WEXITSTATUS(status));
}
else
{
LOG(Warn,"Child exited abnormally");
return -1;
}
}
else
{
LOG(Warn,"Failed to get exit status of child " + intToStr(pid));
return WaitFailed;
}
waitpid(pid,&status,0);
return status;
}
#endif
@ -83,17 +63,14 @@ void ProcessUtils::runAsync(const std::string& executable,
}
int ProcessUtils::runElevated(const std::string& executable,
const std::list<std::string>& args,
const std::string& task)
const std::list<std::string>& args)
{
#ifdef PLATFORM_WINDOWS
(void)task;
return runElevatedWindows(executable,args);
#elif defined(PLATFORM_MAC)
(void)task;
return runElevatedMac(executable,args);
#elif defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
return runElevatedLinux(executable,args,task);
#elif defined(PLATFORM_LINUX)
return runElevatedLinux(executable,args);
#endif
}
@ -127,35 +104,16 @@ 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)
const std::list<std::string>& args)
{
std::string task(_task);
if (task.empty())
{
task = FileUtils::fileName(executable.c_str());
}
// try available graphical sudo instances until we find one that works.
// The different sudo front-ends have different behaviors with respect to error codes:
//
// - 'kdesudo': return 1 if the user enters the wrong password 3 times or if
// they cancel elevation
//
// - recent 'gksudo' versions: return 1 if the user enters the wrong password
// : return -1 if the user cancels elevation
//
// - older 'gksudo' versions : return 0 if the user cancels elevation
std::string sudoMessage = FileUtils::fileName(executable.c_str()) + " needs administrative privileges. Please enter your password.";
std::vector<std::string> sudos;
if (getenv("KDE_SESSION_VERSION"))
{
sudos.push_back("kdesudo");
}
sudos.push_back("kdesudo");
sudos.push_back("gksudo");
sudos.push_back("gksu");
for (unsigned int i=0; i < sudos.size(); i++)
{
@ -169,43 +127,31 @@ int ProcessUtils::runElevatedLinux(const std::string& executable,
{
sudoArgs.push_back("-d");
sudoArgs.push_back("--comment");
std::string sudoMessage = task + " needs administrative privileges. Please enter your password.";
sudoArgs.push_back(sudoMessage);
}
else if (sudoBinary == "gksudo")
{
sudoArgs.push_back("--description");
sudoArgs.push_back(task);
}
else
{
sudoArgs.push_back(task);
sudoArgs.push_back(sudoMessage);
}
sudoArgs.push_back("--");
sudoArgs.push_back(executable);
std::copy(args.begin(),args.end(),std::back_inserter(sudoArgs));
// != 255: some sudo has been found and user failed to authenticate
// or user authenticated correctly
int result = ProcessUtils::runSync(sudoBinary,sudoArgs);
LOG(Info,"Tried to use sudo " + sudoBinary + " with response " + intToStr(result));
if (result != RunFailed)
if (result != 255)
{
return result;
break;
}
}
return RunElevatedFailed;
return RUN_ELEVATED_FAILED;
}
#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)
{
@ -298,7 +244,7 @@ int ProcessUtils::runElevatedMac(const std::string& executable,
else
{
LOG(Error,"failed to launch elevated process " + intToStr(status));
return RunElevatedFailed;
return RUN_ELEVATED_FAILED;
}
// If we want to know more information about what has happened:
@ -312,48 +258,38 @@ int ProcessUtils::runElevatedMac(const std::string& executable,
else
{
LOG(Error,"failed to get rights to launch elevated process. status: " + intToStr(status));
return RunElevatedFailed;
return RUN_ELEVATED_FAILED;
}
}
else
{
return RunElevatedFailed;
return RUN_ELEVATED_FAILED;
}
}
#pragma clang diagnostic pop
#endif
// convert a list of arguments in a space-separated string.
// Arguments containing spaces are enclosed in quotes
std::string quoteArgs(const std::list<std::string>& arguments)
#ifdef PLATFORM_WINDOWS
int ProcessUtils::runElevatedWindows(const std::string& executable,
const std::list<std::string>& arguments)
{
std::string quotedArgs;
std::string args;
// quote process arguments
for (std::list<std::string>::const_iterator iter = arguments.begin();
iter != arguments.end();
iter++)
{
std::string arg = *iter;
bool isQuoted = !arg.empty() &&
arg.at(0) == '"' &&
arg.at(arg.size()-1) == '"';
if (!isQuoted && arg.find(' ') != std::string::npos)
{
if (!arg.empty() && arg.at(0) != '"' && arg.at(arg.size()-1) != '"')
{
arg.insert(0,"\"");
arg.append("\"");
}
quotedArgs += arg;
quotedArgs += " ";
}
return quotedArgs;
}
}
#ifdef PLATFORM_WINDOWS
int ProcessUtils::runElevatedWindows(const std::string& executable,
const std::list<std::string>& arguments)
{
std::string args = quoteArgs(arguments);
args += arg;
args += " ";
}
SHELLEXECUTEINFO executeInfo;
ZeroMemory(&executeInfo,sizeof(executeInfo));
@ -369,7 +305,7 @@ int ProcessUtils::runElevatedWindows(const std::string& executable,
if (!ShellExecuteEx(&executeInfo))
{
LOG(Error,"Failed to start with admin priviledges using ShellExecuteEx()");
return RunElevatedFailed;
return RUN_ELEVATED_FAILED;
}
WaitForSingleObject(executeInfo.hProcess, INFINITE);
@ -401,7 +337,7 @@ PLATFORM_PID ProcessUtils::runAsyncUnix(const std::string& executable,
if (execvp(executable.c_str(),argBuffer) == -1)
{
LOG(Error,"error starting child: " + std::string(strerror(errno)));
exit(RunFailed);
exit(1);
}
}
else
@ -413,23 +349,19 @@ PLATFORM_PID ProcessUtils::runAsyncUnix(const std::string& executable,
#endif
#ifdef PLATFORM_WINDOWS
int ProcessUtils::runWindows(const std::string& _executable,
const std::list<std::string>& _args,
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);
std::string commandLine = executable;
for (std::list<std::string>::const_iterator iter = args.begin(); iter != args.end(); iter++)
{
if (!commandLine.empty())
{
commandLine.append(" ");
}
commandLine.append(*iter);
}
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo,sizeof(startupInfo));
@ -455,7 +387,7 @@ int ProcessUtils::runWindows(const std::string& _executable,
if (!result)
{
LOG(Error,"Failed to start child process. " + executable + " Last error: " + intToStr(GetLastError()));
return RunFailed;
return -1;
}
else
{
@ -463,7 +395,7 @@ int ProcessUtils::runWindows(const std::string& _executable,
{
if (WaitForSingleObject(processInfo.hProcess,INFINITE) == WAIT_OBJECT_0)
{
DWORD status = WaitFailed;
DWORD status = -1;
if (GetExitCodeProcess(processInfo.hProcess,&status) != 0)
{
LOG(Error,"Failed to get exit code for process");
@ -473,7 +405,7 @@ int ProcessUtils::runWindows(const std::string& _executable,
else
{
LOG(Error,"Failed to wait for process to finish");
return WaitFailed;
return -1;
}
}
else
@ -488,23 +420,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

@ -16,15 +16,7 @@ class ProcessUtils
/** Status code returned by runElevated() if launching
* the elevated process fails.
*/
RunElevatedFailed = 255,
/** Status code returned by runSync() if the application
* cannot be started.
*/
RunFailed = -8,
/** Status code returned by runSync() if waiting for
* the application to exit and reading its status code fails.
*/
WaitFailed = -1
RUN_ELEVATED_FAILED = 255
};
static PLATFORM_PID currentProcessId();
@ -51,12 +43,11 @@ class ProcessUtils
/** Run a process with administrative privileges and return the
* status code of the process, or 0 on Windows.
*
* Returns RunElevatedFailed if the elevated process could
* Returns RUN_ELEVATED_FAILED if the elevated process could
* not be started.
*/
static int runElevated(const std::string& executable,
const std::list<std::string>& args,
const std::string& task);
const std::list<std::string>& args);
/** Wait for a process to exit.
* Returns true if the process was found and has exited or false
@ -79,8 +70,7 @@ class ProcessUtils
RunAsync
};
static int runElevatedLinux(const std::string& executable,
const std::list<std::string>& args,
const std::string& task);
const std::list<std::string>& args);
static int runElevatedMac(const std::string& executable,
const std::list<std::string>& args);
static int runElevatedWindows(const std::string& executable,

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"];
[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
{
@ -86,6 +90,9 @@ UpdateDialogCocoa::~UpdateDialogCocoa()
[d->pool release];
}
extern unsigned char mac_dock_png[];
extern unsigned int mac_dock_png_len;
void UpdateDialogCocoa::enableDockIcon()
{
// convert the application to a foreground application and in
@ -96,9 +103,16 @@ void UpdateDialogCocoa::enableDockIcon()
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
TransformProcessType(&psn,kProcessTransformToForegroundApplication);
// loading the icon for the app has to be done after
// changing the process type
NSData* iconData = [NSData dataWithBytes:mac_dock_png length:mac_dock_png_len];
NSImage* iconImage = [[NSImage alloc] initWithData: iconData];
[NSApp setApplicationIconImage:iconImage];
[iconImage release];
}
void UpdateDialogCocoa::init(int /* argc */, char** /* argv */)
void UpdateDialogCocoa::init()
{
enableDockIcon();
@ -173,7 +187,6 @@ void UpdateDialogCocoa::updateFinished()
[d->delegate performSelectorOnMainThread:@selector(reportUpdateFinished:)
withObject:nil
waitUntilDone:false];
UpdateDialog::updateFinished();
}
void* UpdateDialogCocoa::createAutoreleasePool()
@ -186,9 +199,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

@ -1,6 +1,5 @@
#include "UpdateInstaller.h"
#include "AppInfo.h"
#include "FileUtils.h"
#include "Log.h"
#include "ProcessUtils.h"
@ -11,8 +10,6 @@ UpdateInstaller::UpdateInstaller()
, m_waitPid(0)
, m_script(0)
, m_observer(0)
, m_forceElevated(false)
, m_autoClose(false)
{
}
@ -46,11 +43,6 @@ void UpdateInstaller::setScript(UpdateScript* script)
m_script = script;
}
void UpdateInstaller::setForceElevated(bool elevated)
{
m_forceElevated = elevated;
}
std::list<std::string> UpdateInstaller::updaterArgs() const
{
std::list<std::string> args;
@ -60,10 +52,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;
}
@ -76,32 +64,6 @@ void UpdateInstaller::reportError(const std::string& error)
}
}
std::string UpdateInstaller::friendlyErrorForError(const FileUtils::IOException& exception) const
{
std::string friendlyError;
switch (exception.type())
{
case FileUtils::IOException::ReadOnlyFileSystem:
#ifdef PLATFORM_MAC
friendlyError = AppInfo::appName() + " was started from a read-only location. "
"Copy it to the Applications folder on your Mac and run "
"it from there.";
#else
friendlyError = AppInfo::appName() + " was started from a read-only location. "
"Re-install it to a location that can be updated and run it from there.";
#endif
break;
case FileUtils::IOException::DiskFull:
friendlyError = "The disk is full. Please free up some space and try again.";
break;
default:
break;
}
return friendlyError;
}
void UpdateInstaller::run() throw ()
{
if (!m_script || !m_script->isValid())
@ -120,7 +82,7 @@ void UpdateInstaller::run() throw ()
{
updaterPath = ProcessUtils::currentProcessPath();
}
catch (const FileUtils::IOException&)
catch (const FileUtils::IOException& ex)
{
LOG(Error,"error reading process path with mode " + intToStr(m_mode));
reportError("Unable to determine path of updater");
@ -142,12 +104,12 @@ void UpdateInstaller::run() throw ()
args.push_back(intToStr(ProcessUtils::currentProcessId()));
int installStatus = 0;
if (m_forceElevated || !checkAccess())
if (!checkAccess())
{
LOG(Info,"Insufficient rights to install app to " + m_installDir + " requesting elevation");
// start a copy of the updater with admin rights
installStatus = ProcessUtils::runElevated(updaterPath,args,AppInfo::name());
installStatus = ProcessUtils::runElevated(updaterPath,args);
}
else
{
@ -175,13 +137,7 @@ void UpdateInstaller::run() throw ()
{
LOG(Info,"Starting update installation");
// the detailed error string returned by the OS
std::string error;
// the message to present to the user. This may be the same
// as 'error' or may be different if a more helpful suggestion
// can be made for a particular problem
std::string friendlyError;
try
{
LOG(Info,"Installing new and updated files");
@ -198,7 +154,6 @@ void UpdateInstaller::run() throw ()
catch (const FileUtils::IOException& exception)
{
error = exception.what();
friendlyError = friendlyErrorForError(exception);
}
catch (const std::string& genericError)
{
@ -208,23 +163,10 @@ void UpdateInstaller::run() throw ()
if (!error.empty())
{
LOG(Error,std::string("Error installing update ") + error);
try
{
revert();
}
catch (const FileUtils::IOException& exception)
{
LOG(Error,"Error reverting partial update " + std::string(exception.what()));
}
revert();
if (m_observer)
{
if (friendlyError.empty())
{
friendlyError = error;
}
m_observer->updateError(friendlyError);
m_observer->updateError(error);
}
}
@ -276,35 +218,21 @@ 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())
{
// locate the package containing the file
if (!file.package.empty())
std::string packageFile = m_packageDir + '/' + file.package + ".zip";
if (!FileUtils::fileExists(packageFile.c_str()))
{
std::string packageFile = m_packageDir + '/' + file.package + ".zip";
if (!FileUtils::fileExists(packageFile.c_str()))
{
throw "Package file does not exist: " + packageFile;
}
throw "Package file does not exist: " + packageFile;
}
// extract the file from the package and copy it to
// the destination
FileUtils::extractFromZip(packageFile.c_str(),file.path.c_str(),destPath.c_str());
}
else
{
// if no package is specified, look for an uncompressed file in the
// root of the package directory
std::string sourceFile = m_packageDir + '/' + FileUtils::fileName(file.path.c_str());
if (!FileUtils::fileExists(sourceFile.c_str()))
{
throw "Source file does not exist: " + sourceFile;
}
FileUtils::copyFile(sourceFile.c_str(),destPath.c_str());
}
// extract the file from the package and copy it to
// the destination
FileUtils::extractFromZip(packageFile.c_str(),file.path.c_str(),destPath.c_str());
// set the permissions on the newly extracted file
FileUtils::chmod(destPath.c_str(),file.permissions);
@ -407,45 +335,27 @@ void UpdateInstaller::setObserver(UpdateObserver* observer)
void UpdateInstaller::restartMainApp()
{
try
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++)
{
std::string command;
std::list<std::string> args;
if (!m_executable.empty())
if (iter->isMainBinary)
{
command = m_installDir + '/' + m_executable;
if (!m_executable_args.empty())
{
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);
ProcessUtils::runAsync(command,args);
}
else
{
LOG(Error,"No main binary specified in update script");
command = m_installDir + '/' + iter->path;
}
}
catch (const std::exception& ex)
if (!command.empty())
{
LOG(Error,"Unable to restart main app " + std::string(ex.what()));
LOG(Info,"Starting main application " + command);
ProcessUtils::runAsync(command,args);
}
else
{
LOG(Error,"No main binary specified in update script");
}
}
@ -459,23 +369,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

@ -1,7 +1,6 @@
#pragma once
#include "Platform.h"
#include "FileUtils.h"
#include "UpdateScript.h"
#include <list>
@ -31,10 +30,6 @@ class UpdateInstaller
void setMode(Mode mode);
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);
@ -56,7 +51,6 @@ class UpdateInstaller
void postInstallUpdate();
std::list<std::string> updaterArgs() const;
std::string friendlyErrorForError(const FileUtils::IOException& ex) const;
Mode m_mode;
std::string m_installDir;
@ -66,9 +60,5 @@ class UpdateInstaller
UpdateScript* m_script;
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);
@ -20,9 +19,6 @@ long long atoll(const char* string)
UpdaterOptions::UpdaterOptions()
: mode(UpdateInstaller::Setup)
, waitPid(0)
, showVersion(false)
, forceElevated(false)
, autoClose(false)
{
}
@ -71,7 +67,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 +111,6 @@ 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,19 +134,7 @@ 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())
{
// if no --install-dir argument is present, try parsing

View file

@ -14,13 +14,8 @@ 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,13 +9,12 @@
#include "tinythread.h"
#if defined(PLATFORM_LINUX) || defined(PLATFORM_FREEBSD)
#include "UpdateDialogGtkFactory.h"
#if defined(PLATFORM_LINUX)
#include "UpdateDialogGtkWrapper.h"
#include "UpdateDialogAscii.h"
#endif
#if defined(PLATFORM_MAC)
#include "MacBundle.h"
#include "UpdateDialogCocoa.h"
#endif
@ -24,11 +23,8 @@
#endif
#include <iostream>
#include <memory>
#define UPDATER_VERSION "0.16"
UpdateDialog* createUpdateDialog();
void runWithUi(int argc, char** argv, UpdateInstaller* installer);
void runUpdaterThread(void* arg)
{
@ -53,77 +49,15 @@ void runUpdaterThread(void* arg)
#endif
}
#ifdef PLATFORM_MAC
extern unsigned char Info_plist[];
extern unsigned int Info_plist_len;
extern unsigned char mac_icns[];
extern unsigned int mac_icns_len;
bool unpackBundle(int argc, char** argv)
{
MacBundle bundle(FileUtils::tempPath(),AppInfo::name());
std::string currentExePath = ProcessUtils::currentProcessPath();
if (currentExePath.find(bundle.bundlePath()) != std::string::npos)
{
// already running from a bundle
return false;
}
LOG(Info,"Creating bundle " + bundle.bundlePath());
// create a Mac app bundle
std::string plistContent(reinterpret_cast<const char*>(Info_plist),Info_plist_len);
std::string iconContent(reinterpret_cast<const char*>(mac_icns),mac_icns_len);
bundle.create(plistContent,iconContent,ProcessUtils::currentProcessPath());
std::list<std::string> args;
for (int i = 1; i < argc; i++)
{
args.push_back(argv[i]);
}
ProcessUtils::runSync(bundle.executablePath(),args);
return true;
}
#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
void* pool = UpdateDialogCocoa::createAutoreleasePool();
#endif
Log::instance()->open(AppInfo::logFilePath());
#ifdef PLATFORM_MAC
// when the updater is run for the first time, create a Mac app bundle
// and re-launch the application from the bundle. This permits
// setting up bundle properties (such as application icon)
if (unpackBundle(argc,argv))
{
return 0;
}
#endif
UpdaterOptions options;
options.parse(argc,argv);
if (options.showVersion)
{
setupConsole();
std::cout << "Update installer version " << UPDATER_VERSION << std::endl;
return 0;
}
UpdateInstaller installer;
UpdateScript script;
@ -144,21 +78,10 @@ int main(int argc, char** argv)
installer.setPackageDir(options.packageDir);
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 +95,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 +143,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

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Note - The name of the application specified here must match the value
returned by AppInfo::name()
!-->
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Mendeley Updater</string>
<key>CFBundleIconFile</key>
<string>Mendeley Updater.icns</string>
<key>CFBundleIdentifier</key>
<string>org.mendeley.MendeleyUpdater</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSMinimumSystemVersion</key>
<string>10.5</string>
<key>LSMinimumSystemVersionByArchitecture</key>
<dict>
<key>i386</key>
<string>10.5.0</string>
<key>x86_64</key>
<string>10.5.0</string>
</dict>
</dict>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

BIN
src/resources/mac-dock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 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,21 @@ 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
)
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

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

View file

@ -1,11 +0,0 @@
#pragma once
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

@ -24,29 +24,14 @@
<package>app-pkg</package>
<is-main-binary>true</is-main-binary>
</file>
<file>
<name>$UPDATER_FILENAME</name>
<hash>$UPDATER_HASH</hash>
<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>
<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,35 +1,27 @@
#!/usr/bin/ruby
require 'fileutils.rb'
require 'find'
require 'rbconfig'
require 'optparse'
# Install directory - this contains a space to check
# for correct escaping of paths when passing comamnd
# line arguments under Windows
INSTALL_DIR = File.expand_path("install dir/")
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 = {
"APP_FILENAME" => APP_NAME,
"UPDATER_FILENAME" => UPDATER_NAME
"APP_FILENAME" => APP_NAME
}
def replace_vars(src_file,dest_file,vars)
@ -42,137 +34,51 @@ 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
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
# 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
BZIP2_AVAILABLE = zip_supports_bzip2(ZIP_TOOL)
if (BZIP2_AVAILABLE)
ZIP_FLAGS = "-Z bzip2"
else
ZIP_FLAGS = ""
end
def create_test_file(name, content)
File.open(name, 'w') do |file|
file.puts content
end
return name
end
force_elevation = false
run_in_debugger = false
OptionParser.new do |parser|
parser.on("-f","--force-elevated","Force the updater to elevate itself") do
force_elevation = true
end
parser.on("-d","--debug","Run the updater under GDB") do
run_in_debugger = true
end
end.parse!
# copy 'src' to 'dest', preserving the attributes
# of 'src'
def copy_file(src, dest)
FileUtils.cp src, dest, :preserve => true
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
#
@ -180,13 +86,8 @@ copy_file "../#{UPDATER_NAME}", "#{PACKAGE_DIR}/#{UPDATER_NAME}"
# make sure that it looks in the correct directory for
# the file_list.xml file and packages
#
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"
puts "Running '#{cmd}'"
system(cmd)
system("#{PACKAGE_DIR}/#{UPDATER_NAME} --install-dir #{INSTALL_DIR} --package-dir #{PACKAGE_DIR} --script file_list.xml")
end
# TODO - Correctly wait until updater has finished
@ -194,31 +95,18 @@ sleep(1)
# Check that the app was updated
app_path = "#{INSTALL_DIR}/#{APP_NAME}"
output = `"#{app_path}"`
output = `#{app_path}`
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

@ -40,29 +40,14 @@
<package>app-pkg</package>
<is-main-binary>true</is-main-binary>
</file>
<file>
<name>$UPDATER_FILENAME</name>
<hash>$UPDATER_HASH</hash>
<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
@ -338,9 +339,6 @@ package_file_map.each do |package,files|
# do not package the updater binary into a zip file -
# it must be downloaded uncompressed
if package_config.is_updater(file)
if (!updater_binary_input_path)
updater_binary_input_path = file
end
next
end
quoted_files << "\"#{strip_prefix(file,input_dir)}\""
@ -380,13 +378,12 @@ package_file_map.each do |package,files|
end
# copy the updater to the output directory
puts "Using updater binary: #{updater_binary_input_path}"
if !updater_binary_input_path
puts "Updater binary not found in input directory: #{input_dir}"
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,