Reduce duplication between the different platform implementations of the update dialog

Add an UpdateDialog base class which the different platform
implmentations implement and use this to:

 * Remove the duplicated runWithUi() functions in main.cpp
 * Reduce the amount of boilerplate for loading the GTK dialog at
   runtime and falling back to the non-GTK 'dialog' if this fails.

MD-19678 #time 1h
This commit is contained in:
Robert Knight 2013-08-30 10:52:05 +01:00
parent b1bf64a671
commit c09e5d2290
19 changed files with 178 additions and 210 deletions

View File

@ -82,8 +82,8 @@ if (LINUX)
generate_cpp_resource_file(resource_updatergtk ${CMAKE_CURRENT_BINARY_DIR} ${GTK_UPDATER_LIB} ${GTK_BIN_CPP_FILE})
add_dependencies(resource_updatergtk updatergtk)
set(SOURCES ${SOURCES} UpdateDialogGtkWrapper.cpp ${GTK_BIN_CPP_FILE})
set(HEADERS ${HEADERS} UpdateDialogGtkWrapper.h)
set(SOURCES ${SOURCES} UpdateDialogGtkFactory.cpp ${GTK_BIN_CPP_FILE})
set(HEADERS ${HEADERS} UpdateDialogGtkFactory.h)
endif()
if (APPLE)

14
src/UpdateDialog.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "UpdateObserver.h"
class UpdateDialog : public UpdateObserver
{
public:
virtual ~UpdateDialog() {};
virtual void init(int argc, char** argv) = 0;
virtual void exec() = 0;
virtual void quit() = 0;
};

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()
void UpdateDialogAscii::init(int /* argc */, char** /* argv */)
{
const char* path = "/tmp/update-progress";
m_output.open(path);
@ -58,4 +58,11 @@ void UpdateDialogAscii::updateFinished()
m_mutex.unlock();
}
void UpdateDialogAscii::quit()
{
}
void UpdateDialogAscii::exec()
{
}

View File

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

View File

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

View File

@ -98,7 +98,7 @@ void UpdateDialogCocoa::enableDockIcon()
TransformProcessType(&psn,kProcessTransformToForegroundApplication);
}
void UpdateDialogCocoa::init()
void UpdateDialogCocoa::init(int /* argc */, char** /* argv */)
{
enableDockIcon();
@ -185,4 +185,9 @@ void UpdateDialogCocoa::releaseAutoreleasePool(void* arg)
[(id)arg release];
}
void UpdateDialogCocoa::quit()
{
[NSApp stop:nil];
}

View File

@ -6,31 +6,9 @@
#include <glib.h>
#include <gtk/gtk.h>
UpdateDialogGtk* update_dialog_gtk_new(int argc, char** argv)
UpdateDialogGtk* update_dialog_gtk_new()
{
UpdateDialogGtk* dialog = new UpdateDialogGtk;
dialog->init(argc,argv);
return dialog;
}
void update_dialog_gtk_exec(UpdateDialogGtk* dialog)
{
dialog->exec();
}
void update_dialog_gtk_handle_error(UpdateDialogGtk* dialog, const std::string& errorMessage)
{
dialog->updateError(errorMessage);
}
void update_dialog_gtk_handle_progress(UpdateDialogGtk* dialog, int percentage)
{
dialog->updateProgress(percentage);
}
void update_dialog_gtk_handle_finished(UpdateDialogGtk* dialog)
{
dialog->updateFinished();
return new UpdateDialogGtk();
}
UpdateDialogGtk::UpdateDialogGtk()
@ -95,6 +73,12 @@ void UpdateDialogGtk::exec()
}
void UpdateDialogGtk::finish(GtkWidget* widget, gpointer _dialog)
{
UpdateDialogGtk* dialog = static_cast<UpdateDialogGtk*>(_dialog);
dialog->quit();
}
void UpdateDialogGtk::quit()
{
gtk_main_quit();
}

View File

@ -1,17 +1,20 @@
#pragma once
#include "UpdateDialog.h"
#include "UpdateMessage.h"
#include "UpdateObserver.h"
#include <gtk/gtk.h>
class UpdateDialogGtk : public UpdateObserver
class UpdateDialogGtk : public UpdateDialog
{
public:
UpdateDialogGtk();
void init(int argc, char** argv);
void exec();
// implements UpdateDialog
virtual void init(int argc, char** argv);
virtual void exec();
virtual void quit();
// observer callbacks - these may be called
// from a background thread
@ -33,11 +36,7 @@ class UpdateDialogGtk : public UpdateObserver
// helper functions which allow the GTK dialog to be loaded dynamically
// at runtime and used only if the GTK libraries are actually present
extern "C" {
UpdateDialogGtk* update_dialog_gtk_new(int argc, char** argv);
void update_dialog_gtk_exec(UpdateDialogGtk* dialog);
void update_dialog_gtk_handle_error(UpdateDialogGtk* dialog, const std::string& errorMessage);
void update_dialog_gtk_handle_progress(UpdateDialogGtk* dialog, int percentage);
void update_dialog_gtk_handle_finished(UpdateDialogGtk* dialog);
UpdateDialogGtk* update_dialog_gtk_new();
}

View File

@ -0,0 +1,63 @@
#include "UpdateDialogGtkFactory.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;
#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;
}
UpdateDialog* UpdateDialogGtkFactory::createDialog()
{
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)));
return 0;
}
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);
return reinterpret_cast<UpdateDialog*>(update_dialog_gtk_new());
}

View File

@ -0,0 +1,13 @@
#pragma once
class UpdateDialog;
/** Factory for loading the GTK version of the update dialog
* dynamically at runtime if the GTK libraries are available.
*/
class UpdateDialogGtkFactory
{
public:
static UpdateDialog* createDialog();
};

View File

@ -1,95 +0,0 @@
#include "UpdateDialogGtkWrapper.h"
#include "Log.h"
#include "StringUtils.h"
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
// GTK updater UI library embedded into
// the updater binary
extern unsigned char libupdatergtk_so[];
extern unsigned int libupdatergtk_so_len;
// pointers to helper functions in the GTK updater UI library
UpdateDialogGtk* (*update_dialog_gtk_new)(int,char**) = 0;
void (*update_dialog_gtk_exec)(UpdateDialogGtk* dialog) = 0;
void (*update_dialog_gtk_handle_error)(UpdateDialogGtk* dialog, const std::string& errorMessage) = 0;
void (*update_dialog_gtk_handle_progress)(UpdateDialogGtk* dialog, int percentage) = 0;
void (*update_dialog_gtk_handle_finished)(UpdateDialogGtk* dialog) = 0;
#define BIND_FUNCTION(library,function) \
function = reinterpret_cast<typeof(function)>(dlsym(library,#function));
bool extractFileFromBinary(const char* path, const void* buffer, size_t length)
{
int fd = open(path,O_CREAT | O_WRONLY | O_TRUNC,0755);
size_t count = write(fd,buffer,length);
if (fd < 0 || count < length)
{
if (fd >= 0)
{
close(fd);
}
return false;
}
close(fd);
return true;
}
UpdateDialogGtkWrapper::UpdateDialogGtkWrapper()
: m_dialog(0)
{
}
bool UpdateDialogGtkWrapper::init(int argc, char** argv)
{
const char* libPath = "/tmp/libupdatergtk.so";
if (!extractFileFromBinary(libPath,libupdatergtk_so,libupdatergtk_so_len))
{
LOG(Warn,"Failed to load the GTK UI library - " + std::string(strerror(errno)));
}
void* gtkLib = dlopen(libPath,RTLD_LAZY);
if (!gtkLib)
{
LOG(Warn,"Failed to load the GTK UI - " + std::string(dlerror()));
return false;
}
BIND_FUNCTION(gtkLib,update_dialog_gtk_new);
BIND_FUNCTION(gtkLib,update_dialog_gtk_exec);
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_error);
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_progress);
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_finished);
m_dialog = update_dialog_gtk_new(argc,argv);
return true;
}
void UpdateDialogGtkWrapper::exec()
{
update_dialog_gtk_exec(m_dialog);
}
void UpdateDialogGtkWrapper::updateError(const std::string& errorMessage)
{
update_dialog_gtk_handle_error(m_dialog,errorMessage);
}
void UpdateDialogGtkWrapper::updateProgress(int percentage)
{
update_dialog_gtk_handle_progress(m_dialog,percentage);
}
void UpdateDialogGtkWrapper::updateFinished()
{
update_dialog_gtk_handle_finished(m_dialog);
}

View File

@ -1,30 +0,0 @@
#pragma once
#include "UpdateObserver.h"
class UpdateDialogGtk;
/** A wrapper around UpdateDialogGtk which allows the GTK UI to
* be loaded dynamically at runtime if the GTK libraries are
* available.
*/
class UpdateDialogGtkWrapper : public UpdateObserver
{
public:
UpdateDialogGtkWrapper();
/** Attempt to load and initialize the GTK updater UI.
* If this function returns false, other calls in UpdateDialogGtkWrapper
* may not be used.
*/
bool init(int argc, char** argv);
void exec();
virtual void updateError(const std::string& errorMessage);
virtual void updateProgress(int percentage);
virtual void updateFinished();
private:
UpdateDialogGtk* m_dialog;
};

View File

@ -83,7 +83,7 @@ UpdateDialogWin32::~UpdateDialogWin32()
}
}
void UpdateDialogWin32::init()
void UpdateDialogWin32::init(int /* argc */, char** /* argv */)
{
int width = 300;
int height = 130;
@ -143,6 +143,11 @@ void UpdateDialogWin32::updateFinished()
SendNotifyMessage(m_window.GetHwnd(),WM_USER,reinterpret_cast<WPARAM>(message),0);
}
void UpdateDialogWin32::quit()
{
PostQuitMessage(0);
}
LRESULT WINAPI UpdateDialogWin32::windowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
@ -157,7 +162,7 @@ LRESULT WINAPI UpdateDialogWin32::windowProc(HWND window, UINT message, WPARAM w
{
if (reinterpret_cast<HWND>(lParam) == m_finishButton.GetHwnd())
{
PostQuitMessage(0);
quit();
}
}
break;

View File

@ -8,14 +8,16 @@
#include "controls.h"
#include "stdcontrols.h"
class UpdateDialogWin32 : public UpdateObserver
class UpdateDialogWin32 : public UpdateDialog
{
public:
UpdateDialogWin32();
~UpdateDialogWin32();
void init();
void exec();
// implements UpdateDialog
virtual void init(int argc, char** argv);
virtual void exec();
virtual void quit();
// implements UpdateObserver
virtual void updateError(const std::string& errorMessage);

View File

@ -12,6 +12,7 @@ UpdateInstaller::UpdateInstaller()
, m_script(0)
, m_observer(0)
, m_forceElevated(false)
, m_autoClose(false)
{
}
@ -445,3 +446,8 @@ void UpdateInstaller::postInstallUpdate()
#endif
}
void UpdateInstaller::setAutoClose(bool autoClose)
{
m_autoClose = autoClose;
}

View File

@ -32,6 +32,7 @@ class UpdateInstaller
void setScript(UpdateScript* script);
void setWaitPid(PLATFORM_PID pid);
void setForceElevated(bool elevated);
void setAutoClose(bool autoClose);
void setObserver(UpdateObserver* observer);
@ -64,5 +65,6 @@ class UpdateInstaller
UpdateObserver* m_observer;
std::map<std::string,std::string> m_backups;
bool m_forceElevated;
bool m_autoClose;
};

View File

@ -21,6 +21,7 @@ UpdaterOptions::UpdaterOptions()
, waitPid(0)
, showVersion(false)
, forceElevated(false)
, autoClose(false)
{
}
@ -115,6 +116,7 @@ void UpdaterOptions::parse(int argc, char** argv)
parser.setOption("mode");
parser.setFlag("version");
parser.setFlag("force-elevated");
parser.setFlag("auto-close");
parser.processCommandArgs(argc,argv);
@ -141,6 +143,7 @@ void UpdaterOptions::parse(int argc, char** argv)
showVersion = parser.getFlag("version");
forceElevated = parser.getFlag("force-elevated");
autoClose = parser.getFlag("auto-close");
if (installDir.empty())
{

View File

@ -18,6 +18,7 @@ class UpdaterOptions
std::string logFile;
bool showVersion;
bool forceElevated;
bool autoClose;
private:
void parseOldFormatArgs(int argc, char** argv);

View File

@ -10,7 +10,7 @@
#include "tinythread.h"
#if defined(PLATFORM_LINUX)
#include "UpdateDialogGtkWrapper.h"
#include "UpdateDialogGtkFactory.h"
#include "UpdateDialogAscii.h"
#endif
@ -24,8 +24,9 @@
#endif
#include <iostream>
#include <memory>
#define UPDATER_VERSION "0.15"
#define UPDATER_VERSION "0.16"
void runWithUi(int argc, char** argv, UpdateInstaller* installer);
@ -144,6 +145,7 @@ int main(int argc, char** argv)
installer.setScript(&script);
installer.setWaitPid(options.waitPid);
installer.setForceElevated(options.forceElevated);
installer.setAutoClose(options.autoClose);
if (options.mode == UpdateInstaller::Main)
{
@ -161,41 +163,31 @@ int main(int argc, char** argv)
return 0;
}
#ifdef PLATFORM_LINUX
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
UpdateDialog* createUpdateDialog()
{
UpdateDialogAscii asciiDialog;
UpdateDialogGtkWrapper dialog;
bool useGtk = dialog.init(argc,argv);
if (useGtk)
#if defined(PLATFORM_WINDOWS)
return new UpdateDialogWin32();
#elif defined(PLATFORM_MAC)
return new UpdateDialogCocoa();
#elif defined(PLATFORM_LINUX)
UpdateDialog* dialog = UpdateDialogGtkFactory::createDialog();
if (!dialog)
{
installer->setObserver(&dialog);
dialog = new UpdateDialogAscii();
}
else
{
asciiDialog.init();
installer->setObserver(&asciiDialog);
}
tthread::thread updaterThread(runUpdaterThread,installer);
if (useGtk)
{
dialog.exec();
}
updaterThread.join();
}
return dialog;
#endif
}
#ifdef PLATFORM_MAC
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
{
UpdateDialogCocoa dialog;
installer->setObserver(&dialog);
dialog.init();
tthread::thread updaterThread(runUpdaterThread,installer);
dialog.exec();
std::auto_ptr<UpdateDialog> dialog(createUpdateDialog());
dialog->init(argc, argv);
installer->setObserver(dialog.get());
tthread::thread updaterThread(runUpdaterThread, installer);
dialog->exec();
updaterThread.join();
}
#endif
#ifdef PLATFORM_WINDOWS
// application entry point under Windows
@ -209,14 +201,4 @@ int CALLBACK WinMain(HINSTANCE hInstance,
ProcessUtils::convertWindowsCommandLine(GetCommandLineW(),argc,argv);
return main(argc,argv);
}
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
{
UpdateDialogWin32 dialog;
installer->setObserver(&dialog);
dialog.init();
tthread::thread updaterThread(runUpdaterThread,installer);
dialog.exec();
updaterThread.join();
}
#endif