Portable mode for windows (#2794)

* Portable mode for windows

* fix start menu in portable mode

* Make gcc an happy puppy

* Clean old installation if we are installing over an old portable mode installation

* Default to Desktop\CockatricePortable

* Settings dialog fixes

* wording
This commit is contained in:
ctrlaltca 2017-07-08 11:22:29 +02:00 committed by GitHub
parent 1565309146
commit 1366e5970e
13 changed files with 217 additions and 86 deletions

View file

@ -91,7 +91,6 @@ The following flags can be passed to `cmake`:
- `-DWITH_SERVER=1` Whether to build the server (default 0 = no).
- `-DWITH_CLIENT=0` Whether to build the client (default 1 = yes).
- `-DWITH_ORACLE=0` Whether to build oracle (default 1 = yes).
- `-DPORTABLE=1` Build portable versions of client & oracle (default 0 = no).
- `-DCMAKE_BUILD_TYPE=Debug` Compile in debug mode. Enables extra logging output, debug symbols, and much more verbose compiler warnings (default `Release`).
- `-DUPDATE_TRANSLATIONS=1` Configure `make` to update the translation .ts files for new strings in the source code. Note: Running `make clean` will remove the .ts files (default 0 = no).
- `-DTEST=1` Enable regression tests (default 0 = no). Note: needs googletest, will be downloaded on the fly if unavailable. To run tests: ```make test```.

View file

@ -1,14 +1,24 @@
!include ..\..\..\NSIS.definitions.nsh
!include "MUI2.nsh"
!include "FileFunc.nsh"
Name "@CPACK_PACKAGE_NAME@"
BrandingText "@CPACK_PACKAGE_FILE_NAME@"
OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@"
SetCompressor /SOLID lzma
InstallDir "$PROGRAMFILES\Cockatrice"
!define UNINSTKEY "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice"
!define DEFAULTNORMALDESTINATON "$ProgramFiles\Cockatrice"
!define DEFAULTPORTABLEDESTINATON "$Desktop\CockatricePortable"
!define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@"
RequestExecutionlevel highest
SetCompressor LZMA
Var NormalDestDir
Var PortableDestDir
Var PortableMode
!include LogicLib.nsh
!include FileFunc.nsh
!include MUI2.nsh
!define MUI_ABORTWARNING
!define MUI_WELCOMEFINISHPAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\leftimage.bmp"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\leftimage.bmp"
@ -21,6 +31,8 @@ InstallDir "$PROGRAMFILES\Cockatrice"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\COPYING"
Page Custom PortableModePageCreate PortableModePageLeave
!define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
@ -31,15 +43,150 @@ InstallDir "$PROGRAMFILES\Cockatrice"
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
!insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE English
Function .onInit
StrCpy $NormalDestDir "${DEFAULTNORMALDESTINATON}"
StrCpy $PortableDestDir "${DEFAULTPORTABLEDESTINATON}"
${GetParameters} $9
ClearErrors
${GetOptions} $9 "/?" $8
${IfNot} ${Errors}
MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\
/PORTABLE : Install in portable mode$\n\
/S : Silent install$\n\
/D=%directory% : Specify destination directory$\n"
Quit
${EndIf}
ClearErrors
${GetOptions} $9 "/PORTABLE" $8
${IfNot} ${Errors}
StrCpy $PortableMode 1
StrCpy $0 $PortableDestDir
${Else}
StrCpy $PortableMode 0
StrCpy $0 $NormalDestDir
${If} ${Silent}
Call RequireAdmin
${EndIf}
${EndIf}
${If} $InstDir == ""
; User did not use /D to specify a directory,
; we need to set a default based on the install mode
StrCpy $InstDir $0
${EndIf}
Call SetModeDestinationFromInstdir
FunctionEnd
Function RequireAdmin
UserInfo::GetAccountType
Pop $8
${If} $8 != "admin"
MessageBox MB_ICONSTOP "You need administrator rights to install Cockatrice"
SetErrorLevel 740 ;ERROR_ELEVATION_REQUIRED
Abort
${EndIf}
FunctionEnd
Function SetModeDestinationFromInstdir
${If} $PortableMode = 0
StrCpy $NormalDestDir $InstDir
${Else}
StrCpy $PortableDestDir $InstDir
${EndIf}
FunctionEnd
Function PortableModePageCreate
Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory
!insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice."
nsDialogs::Create 1018
Pop $0
${NSD_CreateLabel} 0 10u 100% 24u "Select install mode:"
Pop $0
${NSD_CreateRadioButton} 30u 50u -30u 8u "Normal installation"
Pop $1
${NSD_CreateRadioButton} 30u 70u -30u 8u "Portable mode (all files in a single folder)"
Pop $2
${If} $PortableMode = 0
SendMessage $1 ${BM_SETCHECK} ${BST_CHECKED} 0
${Else}
SendMessage $2 ${BM_SETCHECK} ${BST_CHECKED} 0
${EndIf}
nsDialogs::Show
FunctionEnd
Function PortableModePageLeave
${NSD_GetState} $1 $0
${If} $0 <> ${BST_UNCHECKED}
StrCpy $PortableMode 0
StrCpy $InstDir $NormalDestDir
Call RequireAdmin
${Else}
StrCpy $PortableMode 1
StrCpy $InstDir $PortableDestDir
${EndIf}
FunctionEnd
Function componentsPagePre
${If} $PortableMode = 0
SetShellVarContext all
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
StrCmp $R0 "" done
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst
Abort
uninst:
ClearErrors
ExecWait "$R0"
done:
${Else}
Abort
${EndIf}
FunctionEnd
Section "Application" SecApplication
SetShellVarContext all
SetOutPath "$INSTDIR"
@CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@
@CPACK_NSIS_FULL_INSTALL@
SetShellVarContext all
SetOutPath "$INSTDIR"
${If} $PortableMode = 1
${AndIf} ${FileExists} "$INSTDIR\portable.dat"
; upgrade portable mode
RMDir /r "$INSTDIR\plugins"
RMDir /r "$INSTDIR\sounds"
RMDir /r "$INSTDIR\themes"
RMDir /r "$INSTDIR\translations"
Delete "$INSTDIR\uninstall.exe"
Delete "$INSTDIR\cockatrice.exe"
Delete "$INSTDIR\oracle.exe"
Delete "$INSTDIR\servatrice.exe"
Delete "$INSTDIR\Qt*.dll"
Delete "$INSTDIR\libmysql.dll"
Delete "$INSTDIR\icu*.dll"
Delete "$INSTDIR\libeay32.dll"
Delete "$INSTDIR\ssleay32.dll"
Delete "$INSTDIR\qt.conf"
Delete "$INSTDIR\qdebug.txt"
Delete "$INSTDIR\servatrice.sql"
Delete "$INSTDIR\servatrice.ini.example"
Delete "$INSTDIR\zlib*.dll"
RMDir "$INSTDIR"
${EndIf}
@CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@
@CPACK_NSIS_FULL_INSTALL@
${If} $PortableMode = 0
WriteUninstaller "$INSTDIR\uninstall.exe"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
@ -49,24 +196,22 @@ Section "Application" SecApplication
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayIcon" "$INSTDIR\cockatrice.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "EstimatedSize" "$0"
SectionEnd
Section "Update configuration" SecUpdateConfig
SetShellVarContext current
WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "carddatabase" "$LOCALAPPDATA\Cockatrice\cards.xml"
WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "tokendatabase" "$LOCALAPPDATA\Cockatrice\tokens.xml"
WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "decks" "$LOCALAPPDATA\Cockatrice\decks"
WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "pics" "$LOCALAPPDATA\Cockatrice\pics"
WriteRegStr HKCU "Software\Cockatrice\Cockatrice\replays" "pics" "$LOCALAPPDATA\Cockatrice\replays"
WriteRegStr HKCU "Software\Cockatrice\Cockatrice\sound" "path" "$INSTDIR\sounds"
${Else}
; Create the file the application uses to detect portable mode
FileOpen $0 "$INSTDIR\portable.dat" w
FileWrite $0 "PORTABLE"
FileClose $0
${EndIf}
SectionEnd
Section "Start menu item" SecStartMenu
${If} $PortableMode = 0
SetShellVarContext all
createDirectory "$SMPROGRAMS\Cockatrice"
createShortCut "$SMPROGRAMS\Cockatrice\Cockatrice.lnk" "$INSTDIR\cockatrice.exe"
createShortCut "$SMPROGRAMS\Cockatrice\Oracle.lnk" "$INSTDIR\oracle.exe"
createShortCut "$SMPROGRAMS\Cockatrice\Servatrice.lnk" "$INSTDIR\servatrice.exe"
${EndIf}
SectionEnd
Section "un.Application" UnSecApplication
@ -105,11 +250,9 @@ Section /o "un.Configurations, decks, cards, pics" UnSecConfiguration
SectionEnd
LangString DESC_SecApplication ${LANG_ENGLISH} "Cockatrice program files"
LangString DESC_SecUpdateConfig ${LANG_ENGLISH} "Update the paths in the application settings according to the installation paths."
LangString DESC_SecStartMenu ${LANG_ENGLISH} "Create start menu items for Cockatrice and Oracle."
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecApplication} $(DESC_SecApplication)
!insertmacro MUI_DESCRIPTION_TEXT ${SecUpdateConfig} $(DESC_SecUpdateConfig)
!insertmacro MUI_DESCRIPTION_TEXT ${SecStartMenu} $(DESC_SecStartMenu)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
@ -119,20 +262,3 @@ LangString DESC_UnSecConfiguration ${LANG_ENGLISH} "Configurations, decks, card
!insertmacro MUI_DESCRIPTION_TEXT ${UnSecApplication} $(DESC_UnSecApplication)
!insertmacro MUI_DESCRIPTION_TEXT ${UnSecConfiguration} $(DESC_UnSecConfiguration)
!insertmacro MUI_UNFUNCTION_DESCRIPTION_END
Function .onInit
SetShellVarContext all
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
StrCmp $R0 "" done
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst
Abort
uninst:
ClearErrors
ExecWait "$R0"
done:
FunctionEnd

View file

@ -308,8 +308,3 @@ Data = Resources\")
install(FILES ${WIN32SSLRUNTIME_LIBRARIES} DESTINATION ./)
endif()
endif()
#Compile a portable version, default off
option(PORTABLE "portable build" OFF)
IF(PORTABLE)
add_definitions(-DPORTABLE_BUILD)
endif()

View file

@ -130,6 +130,21 @@ GeneralSettingsPage::GeneralSettingsPage()
QPushButton *tokenDatabasePathButton = new QPushButton("...");
connect(tokenDatabasePathButton, SIGNAL(clicked()), this, SLOT(tokenDatabasePathButtonClicked()));
if(settingsCache->getIsPortableBuild())
{
deckPathEdit->setEnabled(false);
replaysPathEdit->setEnabled(false);
picsPathEdit->setEnabled(false);
cardDatabasePathEdit->setEnabled(false);
tokenDatabasePathEdit->setEnabled(false);
deckPathButton->setVisible(false);
replaysPathButton->setVisible(false);
picsPathButton->setVisible(false);
cardDatabasePathButton->setVisible(false);
tokenDatabasePathButton->setVisible(false);
}
QGridLayout *pathsGrid = new QGridLayout;
pathsGrid->addWidget(&deckPathLabel, 0, 0);
pathsGrid->addWidget(deckPathEdit, 0, 1);
@ -273,7 +288,14 @@ void GeneralSettingsPage::retranslateUi()
personalGroupBox->setTitle(tr("Personal settings"));
languageLabel.setText(tr("Language:"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
pathsGroupBox->setTitle(tr("Paths"));
if(settingsCache->getIsPortableBuild())
{
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
} else {
pathsGroupBox->setTitle(tr("Paths"));
}
deckPathLabel.setText(tr("Decks directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
picsPathLabel.setText(tr("Pictures directory:"));

View file

@ -10,12 +10,10 @@
QString SettingsCache::getDataPath()
{
return
#ifdef PORTABLE_BUILD
qApp->applicationDirPath() + "/data/";
#else
QStandardPaths::writableLocation(QStandardPaths::DataLocation);
#endif
if(isPortableBuild)
return qApp->applicationDirPath() + "/data/";
else
return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
}
QString SettingsCache::getSettingsPath()
@ -25,9 +23,8 @@ QString SettingsCache::getSettingsPath()
void SettingsCache::translateLegacySettings()
{
#ifdef PORTABLE_BUILD
return;
#endif
if(isPortableBuild)
return;
//Layouts
QFile layoutFile(getSettingsPath()+"layouts/deckLayout.ini");
@ -148,6 +145,11 @@ QString SettingsCache::getSafeConfigFilePath(QString configEntry, QString defaul
}
SettingsCache::SettingsCache()
{
// first, figure out if we are running in portable mode
isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
if(isPortableBuild)
qDebug() << "Portable mode enabled";
// define a dummy context that will be used where needed
QString dummy = QT_TRANSLATE_NOOP("i18n", "English");

View file

@ -115,6 +115,7 @@ private:
QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const;
bool rememberGameSettings;
QList<ReleaseChannel*> releaseChannels;
bool isPortableBuild;
public:
SettingsCache();
@ -198,6 +199,7 @@ public:
MessageSettings& messages() const { return *messageSettings; }
GameFiltersSettings& gameFilters() const { return *gameFiltersSettings; }
LayoutsSettings& layouts() const { return *layoutsSettings; }
bool getIsPortableBuild() const { return isPortableBuild; }
public slots:
void setMainWindowGeometry(const QByteArray &_mainWindowGeometry);
void setTokenDialogGeometry(const QByteArray &_tokenDialog);

View file

@ -106,13 +106,8 @@ QStringMap & SoundEngine::getAvailableThemes()
availableThemes.clear();
// load themes from user profile dir
dir =
#ifdef PORTABLE_BUILD
qApp->applicationDirPath() +
#else
QStandardPaths::standardLocations(QStandardPaths::DataLocation).first() +
#endif
"/sounds";
dir = settingsCache->getDataPath() + "/sounds";
foreach(QString themeName, dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name))
{

View file

@ -38,13 +38,7 @@ QStringMap & ThemeManager::getAvailableThemes()
availableThemes.clear();
// load themes from user profile dir
dir =
#ifdef PORTABLE_BUILD
qApp->applicationDirPath() +
#else
QStandardPaths::standardLocations(QStandardPaths::DataLocation).first() +
#endif
"/themes";
dir = settingsCache->getDataPath() + "/themes";
foreach(QString themeName, dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name))
{

View file

@ -207,8 +207,3 @@ Translations = Resources/translations\")
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/Oracle.exe\" \"\${QTPLUGINS}\" \"${libSearchDirs}\")
" COMPONENT Runtime)
endif()
#Compile a portable version, default off
option(PORTABLE "portable build" OFF)
IF(PORTABLE)
add_definitions(-DPORTABLE_BUILD)
endif()

View file

@ -490,9 +490,6 @@ void SaveSetsPage::retranslateUi()
"Press \"Save\" to save the imported cards to the Cockatrice database."));
defaultPathCheckBox->setText(tr("Save to the default path (recommended)"));
#ifdef PORTABLE_BUILD
defaultPathCheckBox->setEnabled(false);
#endif
}
void SaveSetsPage::updateTotalProgress(int cardsImported, int /* setIndex */, const QString &setName)

View file

@ -195,8 +195,3 @@ Translations = Resources/translations\")
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/Servatrice.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR};${MYSQLCLIENT_LIBRARY_DIR}\")
" COMPONENT Runtime)
endif()
#Compile a portable version, default off
option(PORTABLE "portable build" OFF)
IF(PORTABLE)
add_definitions(-DPORTABLE_BUILD)
endif()

View file

@ -2,10 +2,14 @@
#include <QCoreApplication>
#include <QFile>
#include <QStandardPaths>
#include <QDebug>
SettingsCache::SettingsCache(const QString & fileName, QSettings::Format format, QObject * parent)
:QSettings(fileName, format, parent)
{
// first, figure out if we are running in portable mode
isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
QStringList disallowedRegExpStr = value("users/disallowedregexp", "").toString().split(",", QString::SkipEmptyParts);
disallowedRegExpStr.removeDuplicates();
for (const QString &regExpStr : disallowedRegExpStr) {
@ -16,9 +20,12 @@ SettingsCache::SettingsCache(const QString & fileName, QSettings::Format format,
QString SettingsCache::guessConfigurationPath(QString & specificPath)
{
const QString fileName="servatrice.ini";
#ifdef PORTABLE_BUILD
return fileName;
#endif
if(QFile::exists(qApp->applicationDirPath() + "/portable.dat"))
{
qDebug() << "Portable mode enabled";
return fileName;
}
QString guessFileName;
// specific path
if(!specificPath.isEmpty() && QFile::exists(specificPath))

View file

@ -10,10 +10,12 @@ class SettingsCache : public QSettings {
Q_OBJECT
private:
QSettings *settings;
bool isPortableBuild;
public:
SettingsCache(const QString & fileName="servatrice.ini", QSettings::Format format=QSettings::IniFormat, QObject * parent = 0);
static QString guessConfigurationPath(QString & specificPath);
QList<QRegExp> disallowedRegExp;
bool getIsPortableBuild() const { return isPortableBuild; }
};
extern SettingsCache *settingsCache;