mirror of
https://github.com/etlegacy/Update-Installer.git
synced 2025-02-17 00:41:11 +00:00
Merge branch 'master' of ssh://gitweb/git/desktop/standalone-updater
This commit is contained in:
commit
ef4dc40b52
27 changed files with 467 additions and 152 deletions
|
@ -1,6 +1,7 @@
|
|||
project(updater)
|
||||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
include_directories(external)
|
||||
include_directories(external/TinyThread/source)
|
||||
|
|
84
README
84
README
|
@ -1,6 +1,6 @@
|
|||
This tool is a component of an auto-update system. It is responsible for performing
|
||||
the installation of an update after the necessary files have been downloaded
|
||||
to a temporary directory.
|
||||
This tool is a component of a cross-platform auto-update system.
|
||||
It is responsible for performing the installation of an update after
|
||||
the necessary files have been downloaded to a temporary directory.
|
||||
|
||||
This tool is responsible for:
|
||||
|
||||
|
@ -15,12 +15,12 @@ This tool is responsible for:
|
|||
* Displaying a simple updater UI and re-launching the main application
|
||||
once the update is installed.
|
||||
|
||||
The tool consists of a single small binary which only has a small number of external
|
||||
dependencies that need to be present on the target system.
|
||||
The tool consists of a single small binary which depends only on libraries
|
||||
that are part of the base system.
|
||||
|
||||
The external dependencies of the updater binary are:
|
||||
|
||||
* The C++ runtime library (Linux, Mac),
|
||||
* The C/C++ runtime libraries (Linux, Mac),
|
||||
* pthreads (Linux, Mac),
|
||||
* zlib (Linux, Mac)
|
||||
* native UI library (Win32 API on Windows, Cocoa on Mac, GTK on Linux if available)
|
||||
|
@ -31,3 +31,75 @@ containing the files for the update to a temporary directory. It then needs
|
|||
to invoke the updater, specifying the installation directory, temporary package
|
||||
directory and path to the update script file. The updater then installs the
|
||||
update and restarts the application when done.
|
||||
|
||||
Building the Updater
|
||||
====================
|
||||
|
||||
Create a new directory for the build and from that directory run:
|
||||
|
||||
cmake <path to source directory>
|
||||
make
|
||||
|
||||
Customizing the Updater
|
||||
=======================
|
||||
|
||||
To customize the application name, organization and messages displayed by the updater,
|
||||
edit the AppInfo class and the icons in src/resources
|
||||
|
||||
Preparing an Update
|
||||
===================
|
||||
|
||||
1. Create a directory containing your application's files,
|
||||
laid out 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
|
||||
is available. The installed application then needs to download the
|
||||
relevant packages, file_list.xml file and updater binary to a temporary
|
||||
directory and invoke the updater.
|
||||
|
||||
Delta Updates
|
||||
=============
|
||||
|
||||
The simplest possible auto-update implementation is for existing installs
|
||||
to download a complete copy of the new version and install it. This is
|
||||
appropriate if a full download and install will not take a long time for most users
|
||||
(eg. if the application is small or they have a fast internet connection).
|
||||
|
||||
To reduce the download size, delta updates can be created which only include
|
||||
the necessary files or components to update from the old to the new version.
|
||||
|
||||
The file_list.xml file format can be used to represent either a complete
|
||||
install - in which every file that makes up the application is included,
|
||||
or a delta update - in which case only new or updated files and packages
|
||||
are included.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
|
15
TODO
15
TODO
|
@ -19,7 +19,7 @@ General Updater Tasks:
|
|||
* 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 [partially done - needs to write to correct location]
|
||||
* Write log file entries to an actual log file [Linux, Windows: done, Mac: TODO]
|
||||
* Test updater on an old Windows system without Visual Studio installed and statically
|
||||
link C++ runtime libraries if necessary
|
||||
|
||||
|
@ -34,6 +34,8 @@ Mendeley-specific Updater Tasks:
|
|||
* 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]
|
||||
|
@ -42,13 +44,16 @@ Mendeley Desktop <= 1.0 auto-update system compatibility:
|
|||
Auto-update preparation tools:
|
||||
|
||||
* Tool to create .zip packages for a release and
|
||||
upload them to S3
|
||||
* Tool to generate backwards-compatible structure for XML file
|
||||
* Tool to generate new structure for XML file
|
||||
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)
|
||||
|
||||
Telemetry:
|
||||
* Call a project-specific URL to report successful/failed update installation
|
||||
and starting of new app after update
|
||||
|
@ -57,4 +62,6 @@ 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
|
||||
|
|
21
cmake/modules/GenerateCppResourceFile.cmake
Normal file
21
cmake/modules/GenerateCppResourceFile.cmake
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
# Convert a binary data file into a C++
|
||||
# source file for embedding into an application binary
|
||||
#
|
||||
# Currently only implemented for Unix. Requires the 'xxd'
|
||||
# tool to be installed.
|
||||
#
|
||||
# INPUT_FILE : The name of the binary data file to be converted into a C++
|
||||
# source file.
|
||||
#
|
||||
# CPP_FILE : The path of the C++ source file to be generated.
|
||||
# See the documentation for xxd for information on
|
||||
# the structure of the generated source file.
|
||||
#
|
||||
# INPUT_FILE_TARGET : The name of the target which generates INPUT_FILE
|
||||
#
|
||||
function (generate_cpp_resource_file INPUT_FILE CPP_FILE INPUT_FILE_TARGET)
|
||||
add_custom_command(OUTPUT ${CPP_FILE}
|
||||
COMMAND xxd -i ${INPUT_FILE} ${CPP_FILE}
|
||||
DEPENDS ${INPUT_FILE_TARGET})
|
||||
endfunction()
|
79
src/AppInfo.cpp
Normal file
79
src/AppInfo.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#include "AppInfo.h"
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "Platform.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
#include <stdlib.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
#include <shlobj.h>
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
std::string homeDir()
|
||||
{
|
||||
std::string dir = notNullString(getenv("HOME"));
|
||||
if (!dir.empty())
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
else
|
||||
{
|
||||
// note: if this process has been elevated with sudo,
|
||||
// this will return the home directory of the root user
|
||||
struct passwd* userData = getpwuid(getuid());
|
||||
return notNullString(userData->pw_dir);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string appDataPath(const std::string& organizationName,
|
||||
const std::string& appName)
|
||||
{
|
||||
#ifdef PLATFORM_LINUX
|
||||
std::string xdgDataHome = notNullString(getenv("XDG_DATA_HOME"));
|
||||
if (xdgDataHome.empty())
|
||||
{
|
||||
xdgDataHome = homeDir() + "/.local/share";
|
||||
}
|
||||
xdgDataHome += "/data/" + organizationName + '/' + appName;
|
||||
return xdgDataHome;
|
||||
|
||||
#elif defined(PLATFORM_MAC)
|
||||
// TODO - Mac implementation
|
||||
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
char buffer[MAX_PATH+1];
|
||||
if (SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0 /* hToken */, SHGFP_TYPE_CURRENT, buffer) == S_OK)
|
||||
{
|
||||
std::string path = FileUtils::toUnixPathSeparators(notNullString(buffer));
|
||||
path += '/' + organizationName + '/' + appName;
|
||||
return path;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string AppInfo::logFilePath()
|
||||
{
|
||||
return appDataPath(organizationName(),appName()) + '/' + "update-log.txt";
|
||||
}
|
||||
|
||||
std::string AppInfo::updateErrorMessage(const std::string& details)
|
||||
{
|
||||
std::string result = "There was a problem installing the update:\n\n";
|
||||
result += details;
|
||||
result += "\n\nYou can try downloading and installing the latest version of "
|
||||
"Mendeley Desktop from http://www.mendeley.com/download-mendeley-desktop";
|
||||
return result;
|
||||
}
|
||||
|
|
@ -2,11 +2,24 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
/** This class provides project-specific updater properties,
|
||||
* such as the name of the application being updated and
|
||||
* the path to log details of the update install to.
|
||||
*/
|
||||
class AppInfo
|
||||
{
|
||||
public:
|
||||
// Basic application information
|
||||
static std::string name();
|
||||
static std::string appName();
|
||||
static std::string organizationName();
|
||||
|
||||
static std::string logFilePath();
|
||||
|
||||
/** Returns a message to display to the user in the event
|
||||
* of a problem installing the update.
|
||||
*/
|
||||
static std::string updateErrorMessage(const std::string& details);
|
||||
};
|
||||
|
||||
inline std::string AppInfo::name()
|
||||
|
@ -19,3 +32,8 @@ inline std::string AppInfo::appName()
|
|||
return "Mendeley Desktop";
|
||||
}
|
||||
|
||||
inline std::string AppInfo::organizationName()
|
||||
{
|
||||
return "Mendeley Ltd.";
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
add_subdirectory(tests)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
include(GenerateCppResourceFile)
|
||||
|
||||
if (UNIX)
|
||||
add_definitions(-Wall -Werror -Wconversion)
|
||||
|
@ -23,8 +24,9 @@ endif()
|
|||
add_definitions(-DTIXML_USE_STL)
|
||||
|
||||
set (SOURCES
|
||||
AppInfo.cpp
|
||||
DirIterator.cpp
|
||||
FileOps.cpp
|
||||
FileUtils.cpp
|
||||
Log.cpp
|
||||
ProcessUtils.cpp
|
||||
UpdateInstaller.cpp
|
||||
|
@ -42,7 +44,7 @@ endif()
|
|||
set (HEADERS
|
||||
AppInfo.h
|
||||
DirIterator.h
|
||||
FileOps.h
|
||||
FileUtils.h
|
||||
Log.h
|
||||
ProcessUtils.h
|
||||
UpdateInstaller.h
|
||||
|
@ -59,13 +61,11 @@ if (ENABLE_GTK)
|
|||
# embed the GTK helper library into the updater binary.
|
||||
# At runtime it will be extracted and loaded if the
|
||||
# GTK libraries are available
|
||||
|
||||
set(GTK_UPDATER_LIB libupdatergtk.so)
|
||||
set(GTK_BIN_FILE ${CMAKE_CURRENT_BINARY_DIR}/libupdatergtk.cpp)
|
||||
add_custom_command(OUTPUT ${GTK_BIN_FILE}
|
||||
COMMAND xxd -i ${GTK_UPDATER_LIB} ${GTK_BIN_FILE}
|
||||
DEPENDS updatergtk)
|
||||
set(SOURCES ${SOURCES} UpdateDialogGtkWrapper.cpp ${GTK_BIN_FILE})
|
||||
set(GTK_BIN_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/libupdatergtk.cpp)
|
||||
generate_cpp_resource_file(${GTK_UPDATER_LIB} ${GTK_BIN_CPP_FILE} updatergtk)
|
||||
|
||||
set(SOURCES ${SOURCES} UpdateDialogGtkWrapper.cpp ${GTK_BIN_CPP_FILE})
|
||||
set(HEADERS ${HEADERS} UpdateDialogGtkWrapper.h)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
/** Simple class for iterating over the files in a directory
|
||||
* and reporting their names and types.
|
||||
*/
|
||||
class DirIterator
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "FileOps.h"
|
||||
#include "FileUtils.h"
|
||||
|
||||
#include "DirIterator.h"
|
||||
#include "Log.h"
|
||||
|
@ -20,7 +20,7 @@
|
|||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
FileOps::IOException::IOException(const std::string& error)
|
||||
FileUtils::IOException::IOException(const std::string& error)
|
||||
: m_errno(0)
|
||||
{
|
||||
m_error = error;
|
||||
|
@ -39,11 +39,11 @@ FileOps::IOException::IOException(const std::string& error)
|
|||
#endif
|
||||
}
|
||||
|
||||
FileOps::IOException::~IOException() throw ()
|
||||
FileUtils::IOException::~IOException() throw ()
|
||||
{
|
||||
}
|
||||
|
||||
bool FileOps::fileExists(const char* path) throw (IOException)
|
||||
bool FileUtils::fileExists(const char* path) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
struct stat fileInfo;
|
||||
|
@ -69,7 +69,7 @@ bool FileOps::fileExists(const char* path) throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::setQtPermissions(const char* path, int qtPermissions) throw (IOException)
|
||||
void FileUtils::setQtPermissions(const char* path, int qtPermissions) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
int mode = toUnixPermissions(qtPermissions);
|
||||
|
@ -83,7 +83,7 @@ void FileOps::setQtPermissions(const char* path, int qtPermissions) throw (IOExc
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::moveFile(const char* src, const char* dest) throw (IOException)
|
||||
void FileUtils::moveFile(const char* src, const char* dest) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
if (rename(src,dest) != 0)
|
||||
|
@ -98,7 +98,7 @@ void FileOps::moveFile(const char* src, const char* dest) throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::extractFromZip(const char* zipFilePath, const char* src, const char* dest) throw (IOException)
|
||||
void FileUtils::extractFromZip(const char* zipFilePath, const char* src, const char* dest) throw (IOException)
|
||||
{
|
||||
unzFile zipFile = unzOpen(zipFilePath);
|
||||
int result = unzLocateFile(zipFile,src,0);
|
||||
|
@ -138,7 +138,7 @@ void FileOps::extractFromZip(const char* zipFilePath, const char* src, const cha
|
|||
unzClose(zipFile);
|
||||
}
|
||||
|
||||
void FileOps::mkdir(const char* dir) throw (IOException)
|
||||
void FileUtils::mkdir(const char* dir) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
if (::mkdir(dir,S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0)
|
||||
|
@ -153,7 +153,7 @@ void FileOps::mkdir(const char* dir) throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::rmdir(const char* dir) throw (IOException)
|
||||
void FileUtils::rmdir(const char* dir) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
if (::rmdir(dir) != 0)
|
||||
|
@ -168,7 +168,7 @@ void FileOps::rmdir(const char* dir) throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::createSymLink(const char* link, const char* target) throw (IOException)
|
||||
void FileUtils::createSymLink(const char* link, const char* target) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
if (symlink(target,link) != 0)
|
||||
|
@ -182,7 +182,7 @@ void FileOps::createSymLink(const char* link, const char* target) throw (IOExcep
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::removeFile(const char* src) throw (IOException)
|
||||
void FileUtils::removeFile(const char* src) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
if (unlink(src) != 0)
|
||||
|
@ -225,7 +225,7 @@ void FileOps::removeFile(const char* src) throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string FileOps::fileName(const char* path)
|
||||
std::string FileUtils::fileName(const char* path)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
char* pathCopy = strdup(path);
|
||||
|
@ -240,7 +240,7 @@ std::string FileOps::fileName(const char* path)
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string FileOps::dirname(const char* path)
|
||||
std::string FileUtils::dirname(const char* path)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
char* pathCopy = strdup(path);
|
||||
|
@ -254,18 +254,26 @@ std::string FileOps::dirname(const char* path)
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::touch(const char* path) throw (IOException)
|
||||
void FileUtils::touch(const char* path) throw (IOException)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
// see http://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html
|
||||
int fd = creat(path,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
if (fd != -1)
|
||||
if (fileExists(path))
|
||||
{
|
||||
close(fd);
|
||||
utimensat(AT_FDCWD,path,0 /* use current date/time */,0);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw IOException("Unable to touch file " + std::string(path));
|
||||
int fd = creat(path,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
if (fd != -1)
|
||||
{
|
||||
futimens(fd,0 /* use current date/time */);
|
||||
close(fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw IOException("Unable to touch file " + std::string(path));
|
||||
}
|
||||
}
|
||||
#else
|
||||
HANDLE result = CreateFile(path,GENERIC_WRITE,
|
||||
|
@ -285,7 +293,7 @@ void FileOps::touch(const char* path) throw (IOException)
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileOps::rmdirRecursive(const char* path) throw (IOException)
|
||||
void FileUtils::rmdirRecursive(const char* path) throw (IOException)
|
||||
{
|
||||
// remove dir contents
|
||||
DirIterator dir(path);
|
||||
|
@ -309,7 +317,7 @@ void FileOps::rmdirRecursive(const char* path) throw (IOException)
|
|||
rmdir(path);
|
||||
}
|
||||
|
||||
std::string FileOps::canonicalPath(const char* path)
|
||||
std::string FileUtils::canonicalPath(const char* path)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
// on Linux and Mac OS 10.6, realpath() can allocate the required
|
||||
|
@ -339,7 +347,7 @@ void addFlag(InFlags inFlags, int testBit, OutFlags& outFlags, int setBit)
|
|||
}
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
int FileOps::toUnixPermissions(int qtPermissions)
|
||||
int FileUtils::toUnixPermissions(int qtPermissions)
|
||||
{
|
||||
mode_t result = 0;
|
||||
addFlag(qtPermissions,ReadUser,result,S_IRUSR);
|
||||
|
@ -355,7 +363,7 @@ int FileOps::toUnixPermissions(int qtPermissions)
|
|||
}
|
||||
#endif
|
||||
|
||||
std::string FileOps::toUnixPathSeparators(const std::string& str)
|
||||
std::string FileUtils::toUnixPathSeparators(const std::string& str)
|
||||
{
|
||||
std::string result = str;
|
||||
for (size_t i=0; i < result.size(); i++)
|
||||
|
@ -368,7 +376,7 @@ std::string FileOps::toUnixPathSeparators(const std::string& str)
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string FileOps::tempPath()
|
||||
std::string FileUtils::tempPath()
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
return "/tmp";
|
|
@ -5,9 +5,15 @@
|
|||
|
||||
#include "StringUtils.h"
|
||||
|
||||
class FileOps
|
||||
/** A set of functions for performing common operations
|
||||
* on files, throwing exceptions if an operation fails.
|
||||
*/
|
||||
class FileUtils
|
||||
{
|
||||
public:
|
||||
/** Base class for exceptions reported by
|
||||
* FileUtils methods if an operation fails.
|
||||
*/
|
||||
class IOException : public std::exception
|
||||
{
|
||||
public:
|
||||
|
@ -25,6 +31,7 @@ class FileOps
|
|||
int m_errno;
|
||||
};
|
||||
|
||||
/** Reproduction of Qt's QFile::Permission enum. */
|
||||
enum QtFilePermission
|
||||
{
|
||||
ReadOwner = 0x4000,
|
||||
|
@ -52,25 +59,45 @@ class FileOps
|
|||
*/
|
||||
static void removeFile(const char* src) throw (IOException);
|
||||
|
||||
/** Set the permissions of a file using a combination of flags
|
||||
* from the QtFilePermission enum.
|
||||
*/
|
||||
static void setQtPermissions(const char* path, int permissions) throw (IOException);
|
||||
static bool fileExists(const char* path) throw (IOException);
|
||||
static void moveFile(const char* src, const char* dest) throw (IOException);
|
||||
static void extractFromZip(const char* zipFile, 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);
|
||||
|
||||
/** 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. */
|
||||
static std::string dirname(const char* path);
|
||||
|
||||
/** Remove a directory and all of its contents. */
|
||||
static void rmdirRecursive(const char* dir) throw (IOException);
|
||||
|
||||
/** Return the full, absolute path to a file, resolving any
|
||||
* symlinks and removing redundant sections.
|
||||
*/
|
||||
static std::string canonicalPath(const char* path);
|
||||
|
||||
/** Returns the path to a directory for storing temporary files. */
|
||||
static std::string tempPath();
|
||||
|
||||
/** Extract the file @p src from the zip archive @p zipFile and
|
||||
* write it to @p dest.
|
||||
*/
|
||||
static void extractFromZip(const char* zipFile, const char* src, const char* dest) throw (IOException);
|
||||
|
||||
/** Returns a copy of the path 'str' with Windows-style '\'
|
||||
* dir separators converted to Unix-style '/' separators
|
||||
*/
|
||||
static std::string toUnixPathSeparators(const std::string& str);
|
||||
|
||||
private:
|
||||
static int toUnixPermissions(int qtPermissions);
|
||||
|
||||
// returns a copy of the path 'str' with Windows-style '\'
|
||||
// dir separators converted to Unix-style '/' separators
|
||||
static std::string toUnixPathSeparators(const std::string& str);
|
||||
};
|
||||
|
37
src/Log.cpp
37
src/Log.cpp
|
@ -2,40 +2,13 @@
|
|||
|
||||
#include "Platform.h"
|
||||
#include "StringUtils.h"
|
||||
#include "ProcessUtils.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
Log m_globalLog;
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
pid_t currentProcessId = 0;
|
||||
pid_t processId()
|
||||
{
|
||||
if (currentProcessId == 0)
|
||||
{
|
||||
currentProcessId = getpid();
|
||||
}
|
||||
return currentProcessId;
|
||||
}
|
||||
#else
|
||||
DWORD currentProcessId = 0;
|
||||
DWORD processId()
|
||||
{
|
||||
if (currentProcessId == 0)
|
||||
{
|
||||
currentProcessId = GetCurrentProcessId();
|
||||
}
|
||||
return currentProcessId;
|
||||
}
|
||||
#endif
|
||||
|
||||
Log* Log::instance()
|
||||
{
|
||||
return &m_globalLog;
|
||||
|
@ -68,7 +41,7 @@ void Log::writeToStream(std::ostream& stream, Type type, const char* text)
|
|||
stream << "ERROR ";
|
||||
break;
|
||||
}
|
||||
stream << '(' << intToStr(processId()) << ") " << text << std::endl;
|
||||
stream << '(' << intToStr(ProcessUtils::currentProcessId()) << ") " << text << std::endl;
|
||||
}
|
||||
|
||||
void Log::write(Type type, const char* text)
|
||||
|
@ -80,9 +53,3 @@ void Log::write(Type type, const char* text)
|
|||
}
|
||||
}
|
||||
|
||||
std::string Log::defaultPath()
|
||||
{
|
||||
std::string path = "update-log.txt";
|
||||
return path;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ class Log
|
|||
void write(Type type, const char* text);
|
||||
|
||||
static Log* instance();
|
||||
static std::string defaultPath();
|
||||
|
||||
private:
|
||||
static void writeToStream(std::ostream& stream, Type type, const char* text);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "ProcessUtils.h"
|
||||
|
||||
#include "FileOps.h"
|
||||
#include "FileUtils.h"
|
||||
#include "Platform.h"
|
||||
#include "StringUtils.h"
|
||||
#include "Log.h"
|
||||
|
@ -108,7 +108,7 @@ bool ProcessUtils::waitForProcess(PLATFORM_PID pid)
|
|||
int ProcessUtils::runElevatedLinux(const std::string& executable,
|
||||
const std::list<std::string>& args)
|
||||
{
|
||||
std::string sudoMessage = FileOps::fileName(executable.c_str()) + " needs administrative privileges. Please enter your password.";
|
||||
std::string sudoMessage = FileUtils::fileName(executable.c_str()) + " needs administrative privileges. Please enter your password.";
|
||||
|
||||
std::vector<std::string> sudos;
|
||||
sudos.push_back("kdesudo");
|
||||
|
@ -421,7 +421,7 @@ int ProcessUtils::runWindows(const std::string& executable,
|
|||
std::string ProcessUtils::currentProcessPath()
|
||||
{
|
||||
#ifdef PLATFORM_LINUX
|
||||
std::string path = FileOps::canonicalPath("/proc/self/exe");
|
||||
std::string path = FileUtils::canonicalPath("/proc/self/exe");
|
||||
LOG(Info,"Current process path " + path);
|
||||
return path;
|
||||
#elif defined(PLATFORM_MAC)
|
||||
|
@ -451,7 +451,7 @@ void ProcessUtils::convertWindowsCommandLine(LPCWSTR commandLine, int& argc, cha
|
|||
int length = WideCharToMultiByte(CP_ACP,
|
||||
0 /* flags */,
|
||||
argvUnicode[i],
|
||||
-1, /* argvUnicode is null terminated*/
|
||||
-1, /* argvUnicode is null terminated */
|
||||
buffer,
|
||||
BUFFER_SIZE,
|
||||
0,
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
#include <list>
|
||||
#include <string>
|
||||
|
||||
/** A set of functions to get information about the current
|
||||
* process and launch new processes.
|
||||
*/
|
||||
class ProcessUtils
|
||||
{
|
||||
public:
|
||||
|
@ -18,11 +21,22 @@ class ProcessUtils
|
|||
|
||||
static PLATFORM_PID currentProcessId();
|
||||
|
||||
/** Returns the absolute path to the main binary for
|
||||
* the current process.
|
||||
*/
|
||||
static std::string currentProcessPath();
|
||||
|
||||
/** Start a process and wait for it to finish before
|
||||
* returning its exit code.
|
||||
*
|
||||
* Returns -1 if the process cannot be started.
|
||||
*/
|
||||
static int runSync(const std::string& executable,
|
||||
const std::list<std::string>& args);
|
||||
|
||||
/** Start a process and return without waiting for
|
||||
* it to finish.
|
||||
*/
|
||||
static void runAsync(const std::string& executable,
|
||||
const std::list<std::string>& args);
|
||||
|
||||
|
@ -35,6 +49,10 @@ class ProcessUtils
|
|||
static int runElevated(const std::string& executable,
|
||||
const std::list<std::string>& args);
|
||||
|
||||
/** Wait for a process to exit.
|
||||
* Returns true if the process was found and has exited or false
|
||||
* otherwise.
|
||||
*/
|
||||
static bool waitForProcess(PLATFORM_PID pid);
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
|
|
|
@ -44,9 +44,9 @@ void UpdateDialogGtk::init(int argc, char** argv)
|
|||
|
||||
m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title(GTK_WINDOW(m_window),AppInfo::name().c_str());
|
||||
gtk_window_set_resizable(GTK_WINDOW(m_window),false);
|
||||
|
||||
m_progressLabel = gtk_label_new("Installing Updates");
|
||||
|
||||
GtkWidget* windowLayout = gtk_vbox_new(FALSE,3);
|
||||
GtkWidget* buttonLayout = gtk_hbox_new(FALSE,3);
|
||||
GtkWidget* labelLayout = gtk_hbox_new(FALSE,3);
|
||||
|
@ -56,6 +56,12 @@ void UpdateDialogGtk::init(int argc, char** argv)
|
|||
|
||||
m_progressBar = gtk_progress_bar_new();
|
||||
|
||||
// give the dialog a sensible default size by setting a minimum
|
||||
// width on the progress bar. This is used instead of setting
|
||||
// a default size for the dialog since gtk_window_set_default_size()
|
||||
// is ignored when a dialog is marked as non-resizable
|
||||
gtk_widget_set_usize(m_progressBar,350,-1);
|
||||
|
||||
gtk_signal_connect(GTK_OBJECT(m_finishButton),"clicked",
|
||||
GTK_SIGNAL_FUNC(UpdateDialogGtk::finish),this);
|
||||
|
||||
|
@ -77,7 +83,9 @@ void UpdateDialogGtk::init(int argc, char** argv)
|
|||
gtk_widget_show(m_finishButton);
|
||||
gtk_widget_show(m_progressBar);
|
||||
|
||||
gtk_window_set_resizable(GTK_WINDOW(m_window),false);
|
||||
gtk_window_set_position(GTK_WINDOW(m_window),GTK_WIN_POS_CENTER);
|
||||
|
||||
gtk_widget_show(m_window);
|
||||
}
|
||||
|
||||
|
@ -103,7 +111,7 @@ gboolean UpdateDialogGtk::notify(void* _message)
|
|||
case UpdateMessage::UpdateFailed:
|
||||
{
|
||||
dialog->m_hadError = true;
|
||||
std::string errorMessage = "There was a problem installing the update:\n\n" + message->message;
|
||||
std::string errorMessage = AppInfo::updateErrorMessage(message->message);
|
||||
GtkWidget* errorDialog = gtk_message_dialog_new (GTK_WINDOW(dialog->m_window),
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_ERROR,
|
||||
|
|
|
@ -91,7 +91,7 @@ void UpdateDialogWin32::init()
|
|||
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
||||
m_window.CreateEx(0 /* dwExStyle */,
|
||||
updateDialogClassName /* class name */,
|
||||
AppInfo::name(),
|
||||
AppInfo::name().c_str(),
|
||||
style,
|
||||
0, 0, width, height,
|
||||
0 /* parent */, 0 /* menu */, 0 /* reserved */);
|
||||
|
@ -171,8 +171,7 @@ LRESULT WINAPI UpdateDialogWin32::windowProc(HWND window, UINT message, WPARAM w
|
|||
case UpdateMessage::UpdateFailed:
|
||||
{
|
||||
m_hadError = true;
|
||||
std::string text = "There was a problem installing the update:\n\n" +
|
||||
message->message;
|
||||
std::string text = AppInfo::updateErrorMessage(message->message);
|
||||
MessageBox(m_window.GetHwnd(),text.c_str(),"Update Problem",MB_OK);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "UpdateInstaller.h"
|
||||
|
||||
#include "FileOps.h"
|
||||
#include "FileUtils.h"
|
||||
#include "Log.h"
|
||||
#include "ProcessUtils.h"
|
||||
#include "UpdateObserver.h"
|
||||
|
@ -60,6 +60,7 @@ void UpdateInstaller::reportError(const std::string& error)
|
|||
if (m_observer)
|
||||
{
|
||||
m_observer->updateError(error);
|
||||
m_observer->updateFinished();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,16 +82,20 @@ void UpdateInstaller::run() throw ()
|
|||
{
|
||||
updaterPath = ProcessUtils::currentProcessPath();
|
||||
}
|
||||
catch (const FileOps::IOException& ex)
|
||||
catch (const FileUtils::IOException& ex)
|
||||
{
|
||||
LOG(Error,"error reading process path with mode " + intToStr(m_mode));
|
||||
reportError("Unable to determine path of updater");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_mode == Setup)
|
||||
{
|
||||
LOG(Info,"Waiting for main app process to finish");
|
||||
ProcessUtils::waitForProcess(m_waitPid);
|
||||
if (m_waitPid != 0)
|
||||
{
|
||||
LOG(Info,"Waiting for main app process to finish");
|
||||
ProcessUtils::waitForProcess(m_waitPid);
|
||||
}
|
||||
|
||||
std::list<std::string> args = updaterArgs();
|
||||
args.push_back("--mode");
|
||||
|
@ -143,8 +148,10 @@ void UpdateInstaller::run() throw ()
|
|||
|
||||
LOG(Info,"Removing backups");
|
||||
removeBackups();
|
||||
|
||||
postInstallUpdate();
|
||||
}
|
||||
catch (const FileOps::IOException& exception)
|
||||
catch (const FileUtils::IOException& exception)
|
||||
{
|
||||
error = exception.what();
|
||||
}
|
||||
|
@ -174,9 +181,9 @@ void UpdateInstaller::cleanup()
|
|||
{
|
||||
try
|
||||
{
|
||||
FileOps::rmdirRecursive(m_packageDir.c_str());
|
||||
FileUtils::rmdirRecursive(m_packageDir.c_str());
|
||||
}
|
||||
catch (const FileOps::IOException& ex)
|
||||
catch (const FileUtils::IOException& ex)
|
||||
{
|
||||
LOG(Error,"Error cleaning up updater " + std::string(ex.what()));
|
||||
}
|
||||
|
@ -191,11 +198,11 @@ void UpdateInstaller::revert()
|
|||
const std::string& installedFile = iter->first;
|
||||
const std::string& backupFile = iter->second;
|
||||
|
||||
if (FileOps::fileExists(installedFile.c_str()))
|
||||
if (FileUtils::fileExists(installedFile.c_str()))
|
||||
{
|
||||
FileOps::removeFile(installedFile.c_str());
|
||||
FileUtils::removeFile(installedFile.c_str());
|
||||
}
|
||||
FileOps::moveFile(backupFile.c_str(),installedFile.c_str());
|
||||
FileUtils::moveFile(backupFile.c_str(),installedFile.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,32 +215,32 @@ void UpdateInstaller::installFile(const UpdateScriptFile& file)
|
|||
backupFile(destPath);
|
||||
|
||||
// create the target directory if it does not exist
|
||||
std::string destDir = FileOps::dirname(destPath.c_str());
|
||||
if (!FileOps::fileExists(destDir.c_str()))
|
||||
std::string destDir = FileUtils::dirname(destPath.c_str());
|
||||
if (!FileUtils::fileExists(destDir.c_str()))
|
||||
{
|
||||
FileOps::mkdir(destDir.c_str());
|
||||
FileUtils::mkdir(destDir.c_str());
|
||||
}
|
||||
|
||||
if (target.empty())
|
||||
{
|
||||
// locate the package containing the file
|
||||
std::string packageFile = m_packageDir + '/' + file.package + ".zip";
|
||||
if (!FileOps::fileExists(packageFile.c_str()))
|
||||
if (!FileUtils::fileExists(packageFile.c_str()))
|
||||
{
|
||||
throw "Package file does not exist: " + packageFile;
|
||||
}
|
||||
|
||||
// extract the file from the package and copy it to
|
||||
// the destination
|
||||
FileOps::extractFromZip(packageFile.c_str(),file.path.c_str(),destPath.c_str());
|
||||
FileUtils::extractFromZip(packageFile.c_str(),file.path.c_str(),destPath.c_str());
|
||||
|
||||
// set the permissions on the newly extracted file
|
||||
FileOps::setQtPermissions(destPath.c_str(),file.permissions);
|
||||
FileUtils::setQtPermissions(destPath.c_str(),file.permissions);
|
||||
}
|
||||
else
|
||||
{
|
||||
// create the symlink
|
||||
FileOps::createSymLink(destPath.c_str(),target.c_str());
|
||||
FileUtils::createSymLink(destPath.c_str(),target.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,9 +266,9 @@ void UpdateInstaller::uninstallFiles()
|
|||
for (;iter != m_script->filesToUninstall().end();iter++)
|
||||
{
|
||||
std::string path = m_installDir + '/' + iter->c_str();
|
||||
if (FileOps::fileExists(path.c_str()))
|
||||
if (FileUtils::fileExists(path.c_str()))
|
||||
{
|
||||
FileOps::removeFile(path.c_str());
|
||||
FileUtils::removeFile(path.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -272,15 +279,15 @@ void UpdateInstaller::uninstallFiles()
|
|||
|
||||
void UpdateInstaller::backupFile(const std::string& path)
|
||||
{
|
||||
if (!FileOps::fileExists(path.c_str()))
|
||||
if (!FileUtils::fileExists(path.c_str()))
|
||||
{
|
||||
// no existing file to backup
|
||||
return;
|
||||
}
|
||||
|
||||
std::string backupPath = path + ".bak";
|
||||
FileOps::removeFile(backupPath.c_str());
|
||||
FileOps::moveFile(path.c_str(), backupPath.c_str());
|
||||
FileUtils::removeFile(backupPath.c_str());
|
||||
FileUtils::moveFile(path.c_str(), backupPath.c_str());
|
||||
m_backups[path] = backupPath;
|
||||
}
|
||||
|
||||
|
@ -290,7 +297,7 @@ void UpdateInstaller::removeBackups()
|
|||
for (;iter != m_backups.end();iter++)
|
||||
{
|
||||
const std::string& backupFile = iter->second;
|
||||
FileOps::removeFile(backupFile.c_str());
|
||||
FileUtils::removeFile(backupFile.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,20 +307,20 @@ bool UpdateInstaller::checkAccess()
|
|||
|
||||
try
|
||||
{
|
||||
FileOps::removeFile(testFile.c_str());
|
||||
FileUtils::removeFile(testFile.c_str());
|
||||
}
|
||||
catch (const FileOps::IOException& error)
|
||||
catch (const FileUtils::IOException& error)
|
||||
{
|
||||
LOG(Info,"Removing existing access check file failed " + std::string(error.what()));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FileOps::touch(testFile.c_str());
|
||||
FileOps::removeFile(testFile.c_str());
|
||||
FileUtils::touch(testFile.c_str());
|
||||
FileUtils::removeFile(testFile.c_str());
|
||||
return true;
|
||||
}
|
||||
catch (const FileOps::IOException& error)
|
||||
catch (const FileUtils::IOException& error)
|
||||
{
|
||||
LOG(Info,"checkAccess() failed " + std::string(error.what()));
|
||||
return false;
|
||||
|
@ -351,3 +358,15 @@ void UpdateInstaller::restartMainApp()
|
|||
}
|
||||
}
|
||||
|
||||
void UpdateInstaller::postInstallUpdate()
|
||||
{
|
||||
// perform post-install actions
|
||||
|
||||
#ifdef PLATFORM_MAC
|
||||
// touch the application's bundle directory so that
|
||||
// OS X' Launch Services notices any changes in the application's
|
||||
// Info.plist file.
|
||||
FileUtils::touch(m_installDir.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
|
||||
class UpdateObserver;
|
||||
|
||||
/** Central class responsible for installing updates,
|
||||
* launching an elevated copy of the updater if required
|
||||
* and restarting the main application once the update
|
||||
* is installed.
|
||||
*/
|
||||
class UpdateInstaller
|
||||
{
|
||||
public:
|
||||
|
@ -43,6 +48,7 @@ class UpdateInstaller
|
|||
void installFile(const UpdateScriptFile& file);
|
||||
void backupFile(const std::string& path);
|
||||
void reportError(const std::string& error);
|
||||
void postInstallUpdate();
|
||||
|
||||
std::list<std::string> updaterArgs() const;
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
/** UpdateMessage stores information for a message
|
||||
* about the status of update installation sent
|
||||
* between threads.
|
||||
*/
|
||||
class UpdateMessage
|
||||
{
|
||||
public:
|
||||
|
@ -22,16 +26,17 @@ class UpdateMessage
|
|||
init(0,type);
|
||||
}
|
||||
|
||||
void* receiver;
|
||||
Type type;
|
||||
std::string message;
|
||||
int progress;
|
||||
|
||||
private:
|
||||
void init(void* receiver, Type type)
|
||||
{
|
||||
this->progress = 0;
|
||||
this->receiver = receiver;
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
void* receiver;
|
||||
Type type;
|
||||
std::string message;
|
||||
int progress;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
/** Base class for observers of update installation status.
|
||||
* See UpdateInstaller::setObserver()
|
||||
*/
|
||||
class UpdateObserver
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
|
||||
class TiXmlElement;
|
||||
|
||||
/** Represents a package containing one or more
|
||||
* files for an update.
|
||||
*/
|
||||
class UpdateScriptPackage
|
||||
{
|
||||
public:
|
||||
|
@ -26,6 +29,7 @@ class UpdateScriptPackage
|
|||
}
|
||||
};
|
||||
|
||||
/** Represents a file to be installed as part of an update. */
|
||||
class UpdateScriptFile
|
||||
{
|
||||
public:
|
||||
|
@ -50,11 +54,17 @@ class UpdateScriptFile
|
|||
}
|
||||
};
|
||||
|
||||
/** Stores information about the packages and files included
|
||||
* in an update, parsed from an XML file.
|
||||
*/
|
||||
class UpdateScript
|
||||
{
|
||||
public:
|
||||
UpdateScript();
|
||||
|
||||
/** Initialize this UpdateScript with the script stored
|
||||
* in the XML file at @p path.
|
||||
*/
|
||||
void parse(const std::string& path);
|
||||
|
||||
bool isValid() const;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "UpdateInstaller.h"
|
||||
|
||||
/** Parses the command-line options to the updater binary. */
|
||||
class UpdaterOptions
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "AppInfo.h"
|
||||
#include "Log.h"
|
||||
#include "Platform.h"
|
||||
#include "ProcessUtils.h"
|
||||
|
@ -51,7 +52,7 @@ void runUpdaterThread(void* arg)
|
|||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Log::instance()->open(Log::defaultPath());
|
||||
Log::instance()->open(AppInfo::logFilePath());
|
||||
UpdaterOptions options;
|
||||
options.parse(argc,argv);
|
||||
|
||||
|
|
15
tools/README
15
tools/README
|
@ -3,12 +3,19 @@ This directory contains a set of tools for preparing auto-updates.
|
|||
* create-packages.rb
|
||||
|
||||
Given a directory containing the set of files that make up a release,
|
||||
laid out as they are when installed and a JSON file mapping files
|
||||
to packages, this tool generates a set of packages for the release
|
||||
laid out as they are when installed and a JSON package config file,
|
||||
this tool generates a set of packages for the release
|
||||
and a file_list.xml listing all the files that make up the release.
|
||||
|
||||
* single-package-map.json
|
||||
* config-template.json
|
||||
|
||||
This is the simplest possible package map, where all files for
|
||||
This is a template for the config file that specifies:
|
||||
|
||||
- How to partition the files that make up an installed application
|
||||
into packages.
|
||||
- The path of the main binary to run after the update completes
|
||||
- The name of the updater binary to download as part of the update
|
||||
|
||||
This is the simplest possible package configuration, where all files for
|
||||
a release are placed in a single package. This means that the whole
|
||||
package will need to be downloaded to install the update.
|
||||
|
|
11
tools/config-template.json
Normal file
11
tools/config-template.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"packages" : {
|
||||
"app" : [
|
||||
".*"
|
||||
]
|
||||
},
|
||||
|
||||
"updater-binary" : "updater",
|
||||
|
||||
"main-binary" : "myapp"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/ruby
|
||||
|
||||
require 'fileutils'
|
||||
require 'rubygems'
|
||||
require 'find'
|
||||
require 'json'
|
||||
|
@ -8,10 +9,12 @@ require 'optparse'
|
|||
|
||||
# syntax:
|
||||
#
|
||||
# create-packages.rb <input directory> <package map> <output directory>
|
||||
# create-packages.rb <input directory> <config file> <output directory>
|
||||
#
|
||||
# Takes the set of files that make up a release and splits them up into
|
||||
# a set of .zip packages
|
||||
# a set of .zip packages along with a file_list.xml file listing all
|
||||
# the files in the release and mapping them to their respective
|
||||
# packages.
|
||||
#
|
||||
# Outputs:
|
||||
#
|
||||
|
@ -40,8 +43,8 @@ class UpdateScriptFile
|
|||
# flags from the QFile::Permission enum in Qt
|
||||
# size - The size of the file in bytes
|
||||
# package - The name of the package containing this file
|
||||
attr_reader :path,:hash,:permissions,:size,:package,:target
|
||||
attr_writer :path,:hash,:permissions,:size,:package,:target
|
||||
attr_reader :path,:hash,:permissions,:size,:package,:target,:is_main_binary
|
||||
attr_writer :path,:hash,:permissions,:size,:package,:target,:is_main_binary
|
||||
end
|
||||
|
||||
# Utility method - convert a hash map to an REXML element
|
||||
|
@ -76,12 +79,23 @@ def strip_prefix(string,prefix)
|
|||
end
|
||||
|
||||
class UpdateScriptGenerator
|
||||
def initialize(input_dir,output_dir, file_list, package_file_map)
|
||||
|
||||
# input_dir - The directory containing files that make up the install
|
||||
# output_dir - The directory containing the generated packages
|
||||
# package_config - The PackageConfig specifying the file -> package map
|
||||
# for the application and other config options
|
||||
# file_list - A list of all files in 'input_dir' which make up the install
|
||||
# package_file_map - A map of (package name -> [paths of files in this package])
|
||||
|
||||
def initialize(input_dir, output_dir, package_config, file_list, package_file_map)
|
||||
@config = package_config
|
||||
|
||||
# List of files to install in this version
|
||||
@files_to_install = []
|
||||
file_list.each do |path|
|
||||
file = UpdateScriptFile.new
|
||||
file.path = strip_prefix(path,input_dir)
|
||||
file.is_main_binary = (file.path == package_config.main_binary)
|
||||
|
||||
if (File.symlink?(path))
|
||||
file.target = File.readlink(path)
|
||||
|
@ -112,7 +126,7 @@ class UpdateScriptGenerator
|
|||
end
|
||||
end
|
||||
|
||||
def toXML()
|
||||
def to_xml()
|
||||
doc = REXML::Document.new
|
||||
update_elem = REXML::Element.new("update")
|
||||
doc.add_element update_elem
|
||||
|
@ -128,7 +142,7 @@ class UpdateScriptGenerator
|
|||
|
||||
def deps_to_xml()
|
||||
deps_elem = REXML::Element.new("dependencies")
|
||||
deps = ["updater.exe"]
|
||||
deps = @config.updater_binary
|
||||
deps.each do |dependency|
|
||||
dep_elem = REXML::Element.new("file")
|
||||
dep_elem.text = dependency
|
||||
|
@ -166,6 +180,11 @@ class UpdateScriptGenerator
|
|||
attributes["hash"] = file.hash
|
||||
attributes["package"] = file.package
|
||||
end
|
||||
|
||||
if (file.is_main_binary)
|
||||
attributes["is-main-binary"] = "true"
|
||||
end
|
||||
|
||||
hash_to_xml(file_elem,attributes)
|
||||
end
|
||||
return install_elem
|
||||
|
@ -225,16 +244,21 @@ class UpdateScriptGenerator
|
|||
end
|
||||
end
|
||||
|
||||
class PackageMap
|
||||
class PackageConfig
|
||||
attr_reader :main_binary, :updater_binary
|
||||
|
||||
def initialize(map_file)
|
||||
@rule_map = {}
|
||||
map_json = JSON.parse(File.read(map_file))
|
||||
map_json.each do |package,rules|
|
||||
config_json = JSON.parse(File.read(map_file))
|
||||
config_json["packages"].each do |package,rules|
|
||||
rules.each do |rule|
|
||||
rule_regex = Regexp.new(rule)
|
||||
@rule_map[rule_regex] = package
|
||||
end
|
||||
end
|
||||
|
||||
@main_binary = config_json["main-binary"]
|
||||
@updater_binary = config_json["updater-binary"]
|
||||
end
|
||||
|
||||
def package_for_file(file)
|
||||
|
@ -259,6 +283,8 @@ input_dir = ARGV[0]
|
|||
package_map_file = ARGV[1]
|
||||
output_dir = ARGV[2]
|
||||
|
||||
FileUtils.mkpath(output_dir)
|
||||
|
||||
# get the details of each input file
|
||||
input_file_list = []
|
||||
Find.find(input_dir) do |path|
|
||||
|
@ -269,14 +295,14 @@ end
|
|||
# map each input file to a corresponding package
|
||||
|
||||
# read the package map
|
||||
package_map = PackageMap.new(package_map_file)
|
||||
package_config = PackageConfig.new(package_map_file)
|
||||
|
||||
# map of package name -> array of files
|
||||
package_file_map = {}
|
||||
input_file_list.each do |file|
|
||||
next if File.symlink?(file)
|
||||
|
||||
package = package_map.package_for_file(file)
|
||||
package = package_config.package_for_file(file)
|
||||
if (!package)
|
||||
raise "Unable to find package for file #{file}"
|
||||
end
|
||||
|
@ -295,18 +321,22 @@ package_file_map.each do |package,files|
|
|||
quoted_file_list = quoted_files.join(" ")
|
||||
|
||||
output_path = File.expand_path(output_dir)
|
||||
output_file = "#{output_path}/#{package}.zip"
|
||||
|
||||
File.unlink(output_file) if File.exist?(output_file)
|
||||
|
||||
Dir.chdir(input_dir) do
|
||||
#if (!system("zip #{output_path}/#{package}.zip #{quoted_file_list}"))
|
||||
# raise "Failed to generate package #{package}"
|
||||
# end
|
||||
if (!system("zip #{output_path}/#{package}.zip #{quoted_file_list}"))
|
||||
raise "Failed to generate package #{package}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# output the file_list.xml file
|
||||
update_script = UpdateScriptGenerator.new(input_dir,output_dir,input_file_list,package_file_map)
|
||||
update_script = UpdateScriptGenerator.new(input_dir,output_dir,package_config,input_file_list,package_file_map)
|
||||
output_xml_file = "#{output_dir}/file_list.unformatted.xml"
|
||||
File.open(output_xml_file,'w') do |file|
|
||||
file.write update_script.toXML()
|
||||
file.write update_script.to_xml()
|
||||
end
|
||||
|
||||
# xmllint generates more readable formatted XML than REXML, so write unformatted
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"app" : [
|
||||
".*"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue