diff --git a/code/ryzom/tools/client/CMakeLists.txt b/code/ryzom/tools/client/CMakeLists.txt index 3cdaf0136..7fa015c47 100644 --- a/code/ryzom/tools/client/CMakeLists.txt +++ b/code/ryzom/tools/client/CMakeLists.txt @@ -3,6 +3,7 @@ IF(WITH_RYZOM_CLIENT) IF(WITH_QT OR WITH_QT5) ADD_SUBDIRECTORY(client_config_qt) + ADD_SUBDIRECTORY(ryzom_installer) ENDIF() ENDIF() diff --git a/code/ryzom/tools/client/ryzom_installer/CMakeLists.txt b/code/ryzom/tools/client/ryzom_installer/CMakeLists.txt new file mode 100644 index 000000000..4471628e0 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/CMakeLists.txt @@ -0,0 +1,51 @@ +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR} ${NEL_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/ryzom/client/src/seven_zip ${ZLIB_INCLUDE_DIR}) + +FILE(GLOB SRC src/*.cpp src/*.h res/*.rc) +FILE(GLOB CLIENT_INSTALL_HDR src/*.h) +FILE(GLOB CLIENT_INSTALL_UIS ui/*.ui) +FILE(GLOB CLIENT_INSTALL_TRANS translations/*.ts) +FILE(GLOB CLIENT_INSTALL_RCS res/*.qrc) + +#CONFIGURE_FILE(translations/translations.qrc ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc COPYONLY) +#SET(CLIENT_INSTALL_RCS resources.qrc ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) + +IF(WITH_QT) + INCLUDE_DIRECTORIES(${QT_INCLUDES}) + INCLUDE(${QT_USE_FILE}) + + QT4_ADD_TRANSLATION(CLIENT_INSTALL_QM ${CLIENT_INSTALL_TRANS}) + QT4_ADD_RESOURCES(CLIENT_INSTALL_RC_SRCS ${CLIENT_INSTALL_RCS}) + QT4_WRAP_CPP(CLIENT_INSTALL_MOC_SRC ${CLIENT_INSTALL_HDR}) + QT4_WRAP_UI(CLIENT_INSTALL_UI_HDRS ${CLIENT_INSTALL_UIS}) + + ADD_DEFINITIONS(${QT_DEFINITIONS}) +ELSE() + IF(WIN32) + FIND_PACKAGE(Qt5WinExtras) + SET(QT_LIBRARIES Qt5::WinExtras ${QT_LIBRARIES}) + ENDIF() + + QT5_ADD_TRANSLATION(CLIENT_INSTALL_QM ${CLIENT_INSTALL_TRANS}) + QT5_ADD_RESOURCES(CLIENT_INSTALL_RC_SRCS ${CLIENT_INSTALL_RCS}) + QT5_WRAP_CPP(CLIENT_INSTALL_MOC_SRC ${CLIENT_INSTALL_HDR}) + QT5_WRAP_UI(CLIENT_INSTALL_UI_HDRS ${CLIENT_INSTALL_UIS}) +ENDIF() + +SOURCE_GROUP("Source" FILES ${SRC}) +SOURCE_GROUP("Resources" FILES ${CLIENT_INSTALL_RCS}) +SOURCE_GROUP("Forms" FILES ${CLIENT_INSTALL_UIS}) +SOURCE_GROUP("Generated Files" FILES ${CLIENT_INSTALL_UI_HDRS} ${CLIENT_INSTALL_MOC_SRC} ${CLIENT_INSTALL_RC_SRCS}) +SOURCE_GROUP("Translation Files" FILES ${CLIENT_INSTALL_TRANS}) + +ADD_EXECUTABLE(ryzom_installer_qt WIN32 ${SRC} ${CLIENT_INSTALL_MOC_SRC} ${CLIENT_INSTALL_UI_HDRS} ${CLIENT_INSTALL_RC_SRCS} ${CLIENT_INSTALL_TRANS} ${CLIENT_INSTALL_QM}) +NL_DEFAULT_PROPS(ryzom_installer_qt "Ryzom, Tools: Ryzom Installer" ) +NL_ADD_RUNTIME_FLAGS(ryzom_installer_qt) +NL_ADD_LIB_SUFFIX(ryzom_installer_qt) +TARGET_LINK_LIBRARIES(ryzom_installer_qt nelmisc ryzom_sevenzip ${QT_LIBRARIES}) + +IF(WITH_PCH) + ADD_NATIVE_PRECOMPILED_HEADER(ryzom_installer_qt ${CMAKE_CURRENT_SOURCE_DIR}/src/stdpch.h ${CMAKE_CURRENT_SOURCE_DIR}/src/stdpch.cpp) +ENDIF() + +INSTALL(TARGETS ryzom_installer_qt RUNTIME DESTINATION ${RYZOM_GAMES_PREFIX} COMPONENT client) diff --git a/code/ryzom/tools/client/ryzom_installer/res/background.png b/code/ryzom/tools/client/ryzom_installer/res/background.png new file mode 100644 index 000000000..26548eaa6 Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/background.png differ diff --git a/code/ryzom/tools/client/ryzom_installer/res/modern-header.bmp b/code/ryzom/tools/client/ryzom_installer/res/modern-header.bmp new file mode 100644 index 000000000..dcb0c3c65 Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/modern-header.bmp differ diff --git a/code/ryzom/tools/client/ryzom_installer/res/modern-wizard.bmp b/code/ryzom/tools/client/ryzom_installer/res/modern-wizard.bmp new file mode 100644 index 000000000..9bc88799e Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/modern-wizard.bmp differ diff --git a/code/ryzom/tools/client/ryzom_installer/res/resources.qrc b/code/ryzom/tools/client/ryzom_installer/res/resources.qrc new file mode 100644 index 000000000..0e7224f4e --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/res/resources.qrc @@ -0,0 +1,8 @@ + + + background.png + + + ryzom.ico + + diff --git a/code/ryzom/tools/client/ryzom_installer/res/resources.rc b/code/ryzom/tools/client/ryzom_installer/res/resources.rc new file mode 100644 index 000000000..b58619308 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/res/resources.rc @@ -0,0 +1,39 @@ +#include +#include "config.h" + +IDI_MAIN_ICON ICON DISCARDABLE "ryzom.ico" + +VS_VERSION_INFO VERSIONINFO +FILEVERSION RYZOM_VERSION_RC +PRODUCTVERSION NL_VERSION_RC +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS__WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Ryzom Installer" + VALUE "FileVersion", RYZOM_VERSION + VALUE "LegalCopyright", COPYRIGHT +#ifdef _DEBUG + VALUE "OriginalFilename", "ryzom_installer_d.exe" +#else + VALUE "OriginalFilename", "ryzom_installer_r.exe" +#endif + VALUE "ProductName", "Ryzom Core" + VALUE "ProductVersion", NL_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/code/ryzom/tools/client/ryzom_installer/res/ryzom.ico b/code/ryzom/tools/client/ryzom_installer/res/ryzom.ico new file mode 100644 index 000000000..f2c7e8424 Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/ryzom.ico differ diff --git a/code/ryzom/tools/client/ryzom_installer/src/archive.cpp b/code/ryzom/tools/client/ryzom_installer/src/archive.cpp new file mode 100644 index 000000000..287f559dc --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/archive.cpp @@ -0,0 +1,783 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "archive.h" +#include "utils.h" + +#include "nel/misc/big_file.h" +#include "nel/misc/callback.h" +#include "nel/misc/file.h" +#include "nel/misc/path.h" + +#include "7z.h" +#include "7zAlloc.h" +#include "7zBuf.h" +#include "7zCrc.h" + +#include "qzipreader.h" + +#include + +#include + +#define FILE_ATTRIBUTE_READONLY 0x1 +#define FILE_ATTRIBUTE_HIDDEN 0x2 +#define FILE_ATTRIBUTE_SYSTEM 0x4 +#define FILE_ATTRIBUTE_DIRECTORY 0x10 +#define FILE_ATTRIBUTE_ARCHIVE 0x20 +#define FILE_ATTRIBUTE_DEVICE 0x40 +#define FILE_ATTRIBUTE_NORMAL 0x80 +#define FILE_ATTRIBUTE_TEMPORARY 0x100 +#define FILE_ATTRIBUTE_SPARSE_FILE 0x200 +#define FILE_ATTRIBUTE_REPARSE_POINT 0x400 +#define FILE_ATTRIBUTE_COMPRESSED 0x800 +#define FILE_ATTRIBUTE_OFFLINE 0x1000 +#define FILE_ATTRIBUTE_ENCRYPTED 0x4000 +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */ + +#define FILE_ATTRIBUTE_WINDOWS 0x5fff +#define FILE_ATTRIBUTE_UNIX 0xffff0000 + +bool Set7zFileAttrib(const QString &filename, uint32 fileAttributes) +{ + bool attrReadOnly = (fileAttributes & FILE_ATTRIBUTE_READONLY != 0); + bool attrHidden = (fileAttributes & FILE_ATTRIBUTE_HIDDEN != 0); + bool attrSystem = (fileAttributes & FILE_ATTRIBUTE_SYSTEM != 0); + bool attrDir = (fileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0); + bool attrArchive = (fileAttributes & FILE_ATTRIBUTE_ARCHIVE != 0); + bool attrDevice = (fileAttributes & FILE_ATTRIBUTE_DEVICE != 0); + bool attrNormal = (fileAttributes & FILE_ATTRIBUTE_NORMAL != 0); + bool attrTemp = (fileAttributes & FILE_ATTRIBUTE_TEMPORARY != 0); + bool attrSparceFile = (fileAttributes & FILE_ATTRIBUTE_SPARSE_FILE != 0); + bool attrReparsePoint = (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0); + bool attrCompressed = (fileAttributes & FILE_ATTRIBUTE_COMPRESSED != 0); + bool attrOffline = (fileAttributes & FILE_ATTRIBUTE_OFFLINE != 0); + bool attrEncrypted = (fileAttributes & FILE_ATTRIBUTE_ENCRYPTED != 0); + bool attrUnix = (fileAttributes & FILE_ATTRIBUTE_UNIX_EXTENSION != 0); + + uint32 unixAttributes = (fileAttributes & FILE_ATTRIBUTE_UNIX) >> 16; + uint32 windowsAttributes = fileAttributes & FILE_ATTRIBUTE_WINDOWS; + + qDebug() << "attribs" << QByteArray::fromRawData((const char*)&fileAttributes, 4).toHex(); + +#ifdef Q_OS_WIN + SetFileAttributesW((wchar_t*)filename.utf16(), windowsAttributes); +#else + const char *name = filename.toUtf8().constData(); + + struct stat stat_info; + if (lstat(name, &stat_info)!=0) + { + nlwarning("SetFileAttrib(%s,%d) : false-2-1", (const char *)name, fileAttributes); + return false; + } + + if (attrUnix) + { + stat_info.st_mode = unixAttributes; + + if (S_ISLNK(stat_info.st_mode)) + { + if (convert_to_symlink(name) != 0) + { + nlwarning("SetFileAttrib(%s,%d) : false-3",(const char *)name,fileAttributes); + return false; + } + } + else if (S_ISREG(stat_info.st_mode)) + { + nlwarning("##DBG chmod-2(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask); + chmod(name, stat_info.st_mode & gbl_umask.mask); + } + else if (S_ISDIR(stat_info.st_mode)) + { + // user/7za must be able to create files in this directory + stat_info.st_mode |= (S_IRUSR | S_IWUSR | S_IXUSR); + nlwarning("##DBG chmod-3(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask); + chmod(name, stat_info.st_mode & gbl_umask.mask); + } + } + else if (!S_ISLNK(stat_info.st_mode)) + { + // do not use chmod on a link + + // Only Windows Attributes + if( S_ISDIR(stat_info.st_mode)) + { + // Remark : FILE_ATTRIBUTE_READONLY ignored for directory. + nlwarning("##DBG chmod-4(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask); + chmod(name,stat_info.st_mode & gbl_umask.mask); + } + else + { + // octal!, clear write permission bits + if (fileAttributes & FILE_ATTRIBUTE_READONLY) stat_info.st_mode &= ~0222; + nlwarning("##DBG chmod-5(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask); + chmod(name,stat_info.st_mode & gbl_umask.mask); + } + } +#endif + + return true; +} + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +#define SZ_ERROR_INTERRUPTED 18 + +class Q7zFile : public ISeekInStream +{ + QFile m_file; + +public: + Q7zFile(const QString &filename):m_file(filename) + { + Read = readFunc; + Seek = seekFunc; + } + + ~Q7zFile() + { + } + + bool open() + { + return m_file.open(QFile::ReadOnly); + } + + // the read function called by 7zip to read data + static SRes readFunc(void *object, void *buffer, size_t *size) + { + Q7zFile *me = (Q7zFile*)object; + qint64 len = *size; + len = me->m_file.read((char*)buffer, len); + + if (len == *size) + { + *size = len; + return SZ_OK; + } + else + { + return SZ_ERROR_READ; + } + } + + // the seek function called by seven zip to seek inside stream + static SRes seekFunc(void *object, Int64 *pos, ESzSeek origin) + { + Q7zFile *me = (Q7zFile*)object; + qint64 newPos; + + switch(origin) + { + case SZ_SEEK_SET: newPos = *pos; break; + case SZ_SEEK_CUR: newPos = me->m_file.pos() + *pos; break; + case SZ_SEEK_END: newPos = me->m_file.size() - *pos; break; + } + + if (me->m_file.seek(newPos)) + { + *pos = newPos; + return SZ_OK; + } + else + { + return SZ_ERROR_READ; + } + } +}; + +CArchive::CArchive(QObject *parent):QObject(parent), m_mustStop(false) +{ +} + +CArchive::~CArchive() +{ +} + +bool CArchive::extract(const QString &filename, const QString &dest) +{ + m_filename = filename; + m_dest = dest; + + QFile file(m_filename); + + // open archive file to check format + if (!file.open(QFile::ReadOnly)) return false; + + // read 2 first bytes + QByteArray header = file.read(2); + + // close file + file.close(); + + // create destination directory + QDir dir; + dir.mkpath(dest); + + QFuture future; + + // compare to supported formats and call the appropriate decompressor + if (header == "7z") + { + future = QtConcurrent::run(this, &CArchive::extract7z); + } + else if (header == "PK") + { + future = QtConcurrent::run(this, &CArchive::extractZip); + } + else if (QFileInfo(filename).suffix().toLower() == "bnp") + { + future = QtConcurrent::run(this, &CArchive::extractBnp); + } + else + { + qDebug() << "Unsupported format"; + return false; + } + + return true; +} + +void CArchive::getFilesList(const QString &srcDir, const QString &dstDir, const QStringList &filter, FilesToCopy &files) +{ + QDir dir(srcDir); + + QFileInfoList entries = dir.entryInfoList(filter); + + foreach(const QFileInfo &entry, entries) + { + QString fullPath = entry.absoluteFilePath(); + + QString dstPath = dstDir + "/" + dir.relativeFilePath(fullPath); + + if (entry.isDir()) + { + QDir().mkpath(dstPath); + + QDir subDir(fullPath); + + QDirIterator it(subDir, QDirIterator::Subdirectories); + + while (it.hasNext()) + { + fullPath = it.next(); + + if (it.fileName().startsWith('.')) continue; + + QFileInfo fileInfo = it.fileInfo(); + + dstPath = dstDir + "/" + dir.relativeFilePath(fullPath); + + if (fileInfo.isDir()) + { + QDir().mkpath(dstPath); + } + else + { + FileToCopy file; + file.filename = it.fileName(); + file.src = it.filePath(); + file.dst = dstPath; + file.size = it.fileInfo().size(); + file.date = it.fileInfo().lastModified(); + + files << file; + } + } + } + else + { + FileToCopy file; + file.filename = entry.fileName(); + file.src = entry.filePath(); + file.dst = dstPath; + file.size = entry.size(); + file.date = entry.lastModified(); + + files << file; + } + } +} + +bool CArchive::copyServerFiles() +{ + emit extractPrepare(); + + FilesToCopy files; + + QStringList serverFiles; + serverFiles << "cfg"; + serverFiles << "data"; + serverFiles << "examples"; + serverFiles << "patch"; + serverFiles << "unpack"; + serverFiles << "client_default.cfg"; + + CArchive::getFilesList(m_filename, m_dest, serverFiles, files); + + return copyFiles(files); +} + +bool CArchive::copyProfileFiles() +{ + emit extractPrepare(); + + FilesToCopy files; + + QStringList configFiles; + configFiles << "cache"; + configFiles << "save"; + configFiles << "user"; + configFiles << "screenshots"; + configFiles << "client.cfg"; + configFiles << "*.log"; + + CArchive::getFilesList(m_filename, m_dest, configFiles, files); + + return copyFiles(files); +} + +bool CArchive::copyServerFiles(const QString &src, const QString &dst) +{ + if (src.isEmpty() || dst.isEmpty()) return false; + + m_filename = src; + m_dest = dst; + + // create destination directory + QDir().mkpath(dst); + + QFuture future = QtConcurrent::run(this, &CArchive::copyServerFiles); + + return true; +} + +bool CArchive::copyProfileFiles(const QString &src, const QString &dst) +{ + if (src.isEmpty() || dst.isEmpty()) return false; + + m_filename = src; + m_dest = dst; + + // create destination directory + QDir().mkpath(dst); + + QFuture future = QtConcurrent::run(this, &CArchive::copyProfileFiles); + + return true; +} + +bool CArchive::copyFiles(const FilesToCopy &files) +{ + qint64 totalSize = 0; + + foreach(const FileToCopy &file, files) + { + totalSize += file.size; + + qDebug() << file.filename; + } + + emit extractInit(0, totalSize); + + emit extractStart(); + + qint64 processedSize = 0; + + foreach(const FileToCopy &file, files) + { + if (mustStop()) + { + emit extractStop(); + return true; + } + + emit extractProgress(processedSize, file.filename); + + QFileInfo dstFileInfo(file.dst); + + if (dstFileInfo.size() != file.size || dstFileInfo.lastModified() != file.date) + { + if (!QFile::copy(file.src, file.dst)) + { + emit extractFail(tr("Unable to copy file %1").arg(file.src)); + return false; + } + + if (!NLMISC::CFile::setFileModificationDate(qToUtf8(file.dst), file.date.toTime_t())) + { + qDebug() << "Unable to change date"; + } + } + + processedSize += file.size; + } + + emit extractSuccess(totalSize); + + return true; +} + +bool CArchive::extract7z() +{ + Q7zFile inFile(m_filename); + + if (!inFile.open()) + { + emit extractFail(tr("Unable to open %1").arg(m_filename)); + return false; + } + + emit extractPrepare(); + + UInt16 *temp = NULL; + size_t tempSize = 0; + + // register the files read handlers + CLookToRead lookStream; + lookStream.realStream = &inFile; + LookToRead_CreateVTable(&lookStream, False); + LookToRead_Init(&lookStream); + + // init CRC table + CrcGenerateTable(); + + // init 7z + CSzArEx db; + SzArEx_Init(&db); + + // register allocators + ISzAlloc allocImp; + allocImp.Alloc = SzAlloc; + allocImp.Free = SzFree; + + ISzAlloc allocTempImp; + allocTempImp.Alloc = SzAllocTemp; + allocTempImp.Free = SzFreeTemp; + + qint64 total = 0, totalUncompressed = 0; + QString error; + + // open 7z acrhive + SRes res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp); + + if (res == SZ_OK) + { + // process each file in archive + for (UInt32 i = 0; i < db.NumFiles; ++i) + { + bool isDir = SzArEx_IsDir(&db, i) != 0; + + if (!isDir) total += SzArEx_GetFileSize(&db, i); + } + + emit extractInit(0, total); + + emit extractStart(); + + // variables used for decompression + UInt32 blockIndex = 0xFFFFFFFF; + Byte *outBuffer = NULL; + size_t outBufferSize = 0; + + // process each file in archive + for (UInt32 i = 0; i < db.NumFiles; ++i) + { + if (mustStop()) + { + res = SZ_ERROR_INTERRUPTED; + break; + } + + size_t offset = 0; + size_t outSizeProcessed = 0; + + bool isDir = SzArEx_IsDir(&db, i) != 0; + + size_t len = SzArEx_GetFileNameUtf16(&db, i, NULL); + + if (len > tempSize) + { + SzFree(NULL, temp); + tempSize = len; + temp = (UInt16 *)SzAlloc(NULL, tempSize * sizeof(temp[0])); + if (!temp) + { + res = SZ_ERROR_MEM; + break; + } + } + + SzArEx_GetFileNameUtf16(&db, i, temp); + + QString path = QString::fromUtf16(temp); + QString filename = QFileInfo(path).fileName(); + + if (!isDir) + { + emit extractProgress(totalUncompressed, filename); + + res = SzArEx_Extract(&db, &lookStream.s, i, &blockIndex, &outBuffer, &outBufferSize, + &offset, &outSizeProcessed, &allocImp, &allocTempImp); + + if (res != SZ_OK) break; + } + + QString destPath = m_dest + '/' + path; + + QDir dir; + + if (isDir) + { + dir.mkpath(destPath); + continue; + } + + dir.mkpath(QFileInfo(destPath).absolutePath()); + + QFile outFile(destPath); + + if (!outFile.open(QFile::WriteOnly)) + { + error = tr("Unable to open output file"); + res = SZ_ERROR_FAIL; + break; + } + + size_t processedSize = outFile.write((const char*)(outBuffer + offset), outSizeProcessed); + + if (processedSize != outSizeProcessed) + { + error = tr("Unable to write output file"); + res = SZ_ERROR_FAIL; + break; + } + + outFile.close(); + + totalUncompressed += SzArEx_GetFileSize(&db, i); + + emit extractProgress(totalUncompressed, filename); + + if (SzBitWithVals_Check(&db.Attribs, i)) + Set7zFileAttrib(destPath, db.Attribs.Vals[i]); + } + + IAlloc_Free(&allocImp, outBuffer); + } + + SzArEx_Free(&db, &allocImp); + SzFree(NULL, temp); + + switch(res) + { + case SZ_OK: + emit extractSuccess(totalUncompressed); + return true; + + case SZ_ERROR_INTERRUPTED: + emit extractStop(); + return true; + + case SZ_ERROR_UNSUPPORTED: + error = tr("7zip decoder doesn't support this archive"); + break; + + case SZ_ERROR_MEM: + error = tr("Unable to allocate memory"); + break; + + case SZ_ERROR_CRC: + error = tr("7zip decoder doesn't support this archive"); + break; + + case SZ_ERROR_FAIL: + // error already defined + break; + + default: + error = tr("Error %1").arg(res); + } + + emit extractFail(error); + + return false; +} + +bool CArchive::extractZip() +{ + emit extractPrepare(); + + QZipReader reader(m_filename); + + QDir baseDir(m_dest); + + // create directories first + QList allFiles = reader.fileInfoList(); + + qint64 totalSize = 0, currentSize = 0; + + foreach (const QZipReader::FileInfo &fi, allFiles) + { + if (fi.isDir) + { + const QString absPath = m_dest + QDir::separator() + fi.filePath; + + if (!baseDir.mkpath(fi.filePath)) + { + emit extractFail(tr("Unable to create directory %1").arg(absPath)); + return false; + } + + if (!QFile::setPermissions(absPath, fi.permissions)) + { + emit extractFail(tr("Unable to set permissions of %1").arg(absPath)); + return false; + } + } + + totalSize += fi.size; + } + + emit extractInit(0, totalSize); + emit extractStart(); + + // client won't use symbolic links so don't process them + + foreach (const QZipReader::FileInfo &fi, allFiles) + { + const QString absPath = m_dest + QDir::separator() + fi.filePath; + + if (fi.isFile) + { + if (mustStop()) + { + emit extractStop(); + return true; + } + + QFile f(absPath); + + if (!f.open(QIODevice::WriteOnly)) + { + emit extractFail(tr("Unable to open %1").arg(absPath)); + return false; + } + + currentSize += f.write(reader.fileData(fi.filePath)); + + f.setPermissions(fi.permissions); + f.close(); + + emit extractProgress(currentSize, QFileInfo(absPath).fileName()); + } + } + + emit extractSuccess(totalSize); + + return true; +} + +bool CArchive::progress(const std::string &filename, uint32 currentSize, uint32 totalSize) +{ + if (mustStop()) + { + emit extractStop(); + + return false; + } + + if (currentSize == 0) + { + emit extractInit(0, (qint64)totalSize); + emit extractStart(); + } + + emit extractProgress((qint64)currentSize, qFromUtf8(filename)); + + if (currentSize == totalSize) + { + emit extractSuccess((qint64)totalSize); + } + + return true; +} + +bool CArchive::extractBnp() +{ + QString error; + + emit extractPrepare(); + + NLMISC::CBigFile::TUnpackProgressCallback cbMethod = NLMISC::CBigFile::TUnpackProgressCallback(this, &CArchive::progress); + + try + { + if (NLMISC::CBigFile::unpack(qToUtf8(m_filename), qToUtf8(m_dest), &cbMethod)) + { + return true; + } + + if (mustStop()) + { + // stopped + + return true; + } + + error.clear(); + } + catch(const NLMISC::EDiskFullError &e) + { + error = tr("disk full"); + } + catch(const NLMISC::EWriteError &e) + { + error = tr("unable to write %1").arg(qFromUtf8(e.Filename)); + } + catch(const NLMISC::EReadError &e) + { + error = tr("unable to read %1").arg(qFromUtf8(e.Filename)); + } + catch(const std::exception &e) + { + error = tr("failed (%1)").arg(qFromUtf8(e.what())); + } + + emit extractFail(tr("Unable to unpack %1 to %2: %3").arg(m_filename).arg(m_dest).arg(error)); + + return false; +} + +void CArchive::stop() +{ + QMutexLocker locker(&m_mutex); + + m_mustStop = true; +} + +bool CArchive::mustStop() +{ + QMutexLocker locker(&m_mutex); + + return m_mustStop; +} + diff --git a/code/ryzom/tools/client/ryzom_installer/src/archive.h b/code/ryzom/tools/client/ryzom_installer/src/archive.h new file mode 100644 index 000000000..0f8284b9e --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/archive.h @@ -0,0 +1,96 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef ARCHIVE_H +#define ARCHIVE_H + +/** + * Files copy, decompression, extraction + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CArchive : public QObject +{ + Q_OBJECT + +public: + CArchive(QObject *parent = NULL); + virtual ~CArchive(); + + bool extract(const QString &filename, const QString &dest); + bool copyServerFiles(const QString &src, const QString &dst); + bool copyProfileFiles(const QString &src, const QString &dst); + + void stop(); + bool mustStop(); + +signals: + // emitted when requesting real URL + void extractPrepare(); + + // emitted when we got the initial (local) and total (remote) size of file + void extractInit(qint64 current, qint64 total); + + // emitted when we begin to download + void extractStart(); + + // emitted when the download stopped + void extractStop(); + + // emitted when extracting + void extractProgress(qint64 current, const QString &filename); + + // emitted when the whole file is downloaded + void extractSuccess(qint64 total); + + // emitted when an error occurs + void extractFail(const QString &error); + +protected: + + struct FileToCopy + { + QString filename; + QString src; + QString dst; + qint64 size; + QDateTime date; + }; + + typedef QList FilesToCopy; + + bool extract7z(); + bool extractZip(); + bool extractBnp(); + + bool progress(const std::string &filename, uint32 currentFile, uint32 totalFiles); + + bool copyServerFiles(); + bool copyProfileFiles(); + bool copyFiles(const FilesToCopy &files); + + static void getFilesList(const QString &srcDir, const QString &dstDir, const QStringList &filter, FilesToCopy &files); + + QString m_filename; + QString m_dest; + + QMutex m_mutex; + + bool m_mustStop; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp new file mode 100644 index 000000000..b3b41cfde --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp @@ -0,0 +1,568 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "configfile.h" +#include "utils.h" + +#include "nel/misc/path.h" + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +const CServer NoServer; +const CProfile NoProfile; + +CConfigFile *CConfigFile::s_instance = NULL; + +CConfigFile::CConfigFile(QObject *parent):QObject(parent), m_defaultServer(0), m_defaultProfile(0), m_use64BitsClient(false) +{ + s_instance = this; + + m_defaultConfigPath = QApplication::applicationDirPath() + "/installer.ini"; + m_configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/installer.ini"; +} + +CConfigFile::~CConfigFile() +{ + s_instance = NULL; +} + +bool CConfigFile::load() +{ + return load(m_configPath) || load(m_defaultConfigPath); +} + +bool CConfigFile::load(const QString &filename) +{ + QSettings settings(filename, QSettings::IniFormat); + + settings.beginGroup("common"); + m_language = settings.value("language").toString(); + m_srcDirectory = settings.value("source_directory").toString(); + m_installationDirectory = settings.value("installation_directory").toString(); + m_use64BitsClient = settings.value("use_64bits_client").toBool(); + settings.endGroup(); + + settings.beginGroup("servers"); + int serversCount = settings.value("size").toInt(); + m_defaultServer = settings.value("default").toInt(); + settings.endGroup(); + + m_servers.resize(serversCount); + + for(int i = 0; i < serversCount; ++i) + { + CServer &server = m_servers[i]; + + settings.beginGroup(QString("server_%1").arg(i)); + + server.id = settings.value("id").toString(); + server.name = settings.value("name").toString(); + server.displayUrl = settings.value("display_url").toString(); + server.dataDownloadUrl = settings.value("data_download_url").toString(); + server.dataDownloadFilename = settings.value("data_download_filename").toString(); + server.dataCompressedSize = settings.value("data_compressed_size").toULongLong(); + server.dataUncompressedSize = settings.value("data_uncompressed_size").toULongLong(); + server.clientDownloadUrl = settings.value("client_download_url").toString(); + server.clientDownloadFilename = settings.value("client_download_filename").toString(); +#if defined(Q_OS_WIN) + server.clientFilename = settings.value("client_filename_windows").toString(); +#elif defined(Q_OS_MAC) + server.clientFilename = settings.value("client_filename_osx").toString(); +#else + server.clientFilename = settings.value("client_filename_linux").toString(); +#endif + server.comments = settings.value("comments").toString(); + + settings.endGroup(); + } + + settings.beginGroup("profiles"); + int profilesCounts = settings.value("size").toInt(); + m_defaultProfile = settings.value("default").toInt(); + settings.endGroup(); + + m_profiles.resize(profilesCounts); + + for(int i = 0; i < profilesCounts; ++i) + { + CProfile &profile = m_profiles[i]; + + settings.beginGroup(QString("profile_%1").arg(i)); + + profile.id = settings.value("id").toInt(); + profile.name = settings.value("name").toString(); + profile.account = settings.value("account").toString(); + profile.server = settings.value("server").toString(); + profile.executable = settings.value("executable").toString(); + profile.arguments = settings.value("arguments").toString(); + profile.comments = settings.value("comments").toString(); + profile.desktopShortcut = settings.value("desktop_shortcut").toBool(); + profile.menuShortcut = settings.value("menu_shortcut").toBool(); + + settings.endGroup(); + } + + return !m_servers.isEmpty(); +} + +bool CConfigFile::save() const +{ + QSettings settings(m_configPath, QSettings::IniFormat); + + settings.beginGroup("common"); + settings.setValue("language", m_language); + settings.setValue("source_directory", m_srcDirectory); + settings.setValue("installation_directory", m_installationDirectory); + settings.setValue("use_64bits_client", m_use64BitsClient); + settings.endGroup(); + + settings.beginGroup("servers"); + settings.setValue("size", m_servers.size()); + settings.setValue("default", m_defaultServer); + settings.endGroup(); + + for(int i = 0; i < m_servers.size(); ++i) + { + const CServer &server = m_servers[i]; + + settings.beginGroup(QString("server_%1").arg(i)); + + settings.setValue("id", server.id); + settings.setValue("name", server.name); + settings.setValue("display_url", server.displayUrl); + settings.setValue("data_download_url", server.dataDownloadUrl); + settings.setValue("data_download_filename", server.dataDownloadFilename); + settings.setValue("data_compressed_size", server.dataCompressedSize); + settings.setValue("data_uncompressed_size", server.dataUncompressedSize); + settings.setValue("client_download_url", server.clientDownloadUrl); + settings.setValue("client_download_filename", server.clientDownloadFilename); +#if defined(Q_OS_WIN) + settings.setValue("client_filename_windows", server.clientFilename); +#elif defined(Q_OS_MAC) + settings.setValue("client_filename_osx", server.clientFilename); +#else + settings.setValue("client_filename_linux", server.clientFilename); +#endif + settings.setValue("comments", server.comments); + + settings.endGroup(); + } + + settings.beginGroup("profiles"); + settings.setValue("size", m_profiles.size()); + settings.setValue("default", m_defaultProfile); + settings.endGroup(); + + for(int i = 0; i < m_profiles.size(); ++i) + { + const CProfile &profile = m_profiles[i]; + + settings.beginGroup(QString("profile_%1").arg(i)); + + settings.setValue("id", profile.id); + settings.setValue("name", profile.name); + settings.setValue("account", profile.account); + settings.setValue("server", profile.server); + settings.setValue("executable", profile.executable); + settings.setValue("arguments", profile.arguments); + settings.setValue("comments", profile.comments); + settings.setValue("desktop_shortcut", profile.desktopShortcut); + settings.setValue("menu_shortcut", profile.menuShortcut); + + settings.endGroup(); + } + + return true; +} + +CConfigFile* CConfigFile::getInstance() +{ + return s_instance; +} + +int CConfigFile::getServersCount() const +{ + return m_servers.size(); +} + +const CServer& CConfigFile::getServer(int i) const +{ + if (i < 0) i = m_defaultServer; + + if (i >= m_servers.size()) return NoServer; + + return m_servers.at(i); +} + +const CServer& CConfigFile::getServer(const QString &id) const +{ + for(int i = 0; i < m_servers.size(); ++i) + { + if (m_servers[i].id == id) return m_servers[i]; + } + + // default server + return getServer(); +} + +int CConfigFile::getProfilesCount() const +{ + return m_profiles.size(); +} + +CProfile CConfigFile::getProfile(int i) const +{ + if (i < 0) i = m_defaultProfile; + + if (i >= m_profiles.size()) return NoProfile; + + return m_profiles.at(i); +} + +void CConfigFile::setProfile(int i, const CProfile &profile) +{ + m_profiles[i] = profile; +} + +int CConfigFile::addProfile(const CProfile &profile) +{ + m_profiles.append(profile); + + return m_profiles.size()-1; +} + +void CConfigFile::removeProfile(int i) +{ + m_profiles.removeAt(i); + + // TODO: decalle all profiles and move files +} + +bool CConfigFile::has64bitsOS() +{ + return QSysInfo::currentCpuArchitecture() == "x86_64"; +} + +int CConfigFile::getDefaultProfile() const +{ + return m_defaultProfile; +} + +int CConfigFile::getDefaultServer() const +{ + return m_defaultServer; +} + +bool CConfigFile::isRyzomInstallerConfigured() const +{ + return m_profiles.size() > 0; +} + +QString CConfigFile::getInstallationDirectory() const +{ + return m_installationDirectory; +} + +void CConfigFile::setInstallationDirectory(const QString &directory) +{ + m_installationDirectory = directory; +} + +QString CConfigFile::getSrcServerDirectory() const +{ + return m_srcDirectory; +} + +void CConfigFile::setSrcServerDirectory(const QString &directory) +{ + m_srcDirectory = directory; +} + +QString CConfigFile::getProfileDirectory() const +{ + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); +} + +QString CConfigFile::getSrcProfileDirectory() const +{ + if (QFile::exists(getSrcServerDirectory() + "/client.cfg")) return getSrcServerDirectory(); + + return qFromUtf8(NLMISC::CPath::getApplicationDirectory("Ryzom")); +} + +bool CConfigFile::use64BitsClient() const +{ + return m_use64BitsClient; +} + +void CConfigFile::setUse64BitsClient(bool on) +{ + m_use64BitsClient = on; +} + +QString CConfigFile::expandVariables(const QString &str) +{ + QString res = str; + + res.replace("$TIMESTAMP", QString::number(QDateTime::currentDateTime().toTime_t())); + res.replace("$LANG", m_language); + res.replace("$ARCH", getClientArch()); + + return res; +} + +QString CConfigFile::getClientArch() const +{ +#if defined(Q_OS_WIN) + return QString("win%1").arg(m_use64BitsClient ? 64:32); +#elif defined(Q_OS_MAC) + // only 64 bits clients under OS X, becure there not any 32 bits OS X version anymore + return "osx"; +#else + return QString("linux%1").arg(m_use64BitsClient ? 64:32); +#endif +} + +QString CConfigFile::getCurrentDirectory() +{ + return QDir::current().absolutePath(); +} + +QString CConfigFile::getParentDirectory() +{ + QDir current = QDir::current(); + current.cdUp(); + return current.absolutePath(); +} + +QString CConfigFile::getApplicationDirectory() +{ + return QApplication::applicationDirPath(); +} + +QString CConfigFile::getOldInstallationDirectory() +{ + // HKEY_CURRENT_USER/SOFTWARE/Nevrax/RyzomInstall/InstallId=1917716796 (string) +#if defined(Q_OS_WIN) + // NSIS previous official installer +#ifdef Q_OS_WIN64 + // use WOW6432Node in 64 bits (64 bits OS and 64 bits Installer) because Ryzom old installer was in 32 bits + QSettings settings("HKEY_LOCAL_MACHINE\\Software\\WOW6432Node\\Nevrax\\Ryzom", QSettings::NativeFormat); +#else + QSettings settings("HKEY_LOCAL_MACHINE\\Software\\Nevrax\\Ryzom", QSettings::NativeFormat); +#endif + + if (settings.contains("Ryzom Install Path")) + { + return QDir::fromNativeSeparators(settings.value("Ryzom Install Path").toString()); + } + + // check default directory if registry key not found + return CConfigFile::has64bitsOS() ? "C:/Program Files (x86)/Ryzom":"C:/Program Files/Ryzom"; +#elif defined(Q_OS_MAC) + return "/Applications/Ryzom.app"; +#else + return QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.ryzom"; +#endif +} + +QString CConfigFile::getNewInstallationDirectory() +{ + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); +} + +bool CConfigFile::isRyzomInstalledIn(const QString &directory) const +{ + // check client and data + return isRyzomClientInstalledIn(directory) && areRyzomDataInstalledIn(directory); +} + +bool CConfigFile::areRyzomDataInstalledIn(const QString &directory) const +{ + QDir dir(directory); + + // directory doesn't exist + if (!dir.exists()) return false; + + if (!dir.cd("data") && dir.exists()) return false; + + // at least 200 BNP in data directory + if (dir.entryList(QStringList() << "*.bnp", QDir::Files).size() < 200) return false; + + // TODO: more checks + + return true; +} + +bool CConfigFile::isRyzomClientInstalledIn(const QString &directory) const +{ + QDir dir(directory); + + // directory doesn't exist + if (!dir.exists()) return false; + + // client_default.cfg doesn't exist + if (!dir.exists("client_default.cfg")) return false; + + if (!dir.exists(getServer().clientFilename)) return false; + + // TODO: more checks + + return true; +} + +QString CConfigFile::getClientFullPath() const +{ + QString path = getProfile().executable; + + if (!path.isEmpty()) return path; + + return getInstallationDirectory() + "/" + getServer().id + "/" + getServer().clientFilename; +} + +QString CConfigFile::getSrcServerClientBNPFullPath() const +{ + return QString("%1/unpack/exedll_%2.bnp").arg(getSrcServerDirectory()).arg(getClientArch()); +} + +CConfigFile::InstallationStep CConfigFile::getNextStep() const +{ + // get last used profile + const CProfile &profile = getProfile(); + + // get server used by it or default server + CServer server = getServer(profile.server); + + // no or wrong profile + if (server.id.isEmpty()) + { + // get last used server + server = getServer(); + } + + // no or wrong server + if (server.id.isEmpty()) + { + // get first server + server = getServer(0); + } + + // no server defined, shouldn't happen + if (server.id.isEmpty()) + { + return DisplayNoServerError; + } + + // only show wizard if installation directory undefined + if (getInstallationDirectory().isEmpty()) + { + return ShowWizard; + } + + QString serverDirectory = getInstallationDirectory() + "/" + server.id; + + if (getSrcServerDirectory().isEmpty()) + { + // user decided to download files + + // downloaded files are kept in server directory + QString dataFile = getInstallationDirectory() + "/" + server.dataDownloadFilename; + QString clientFile = getInstallationDirectory() + "/" + server.clientDownloadFilename; + + // data are not copied + if (!areRyzomDataInstalledIn(serverDirectory)) + { + // when file is not finished, it has .part extension + if (!QFile::exists(dataFile)) + { + return DownloadData; + } + + return ExtractDownloadedData; + } + + if (!isRyzomClientInstalledIn(serverDirectory)) + { + // when file is not finished, it has .part extension + if (!QFile::exists(clientFile)) + { + return DownloadClient; + } + + return ExtractDownloadedClient; + } + } + else + { + // user decided to copy files + + // selected directory contains Ryzom files (shouldn't fail) + if (!areRyzomDataInstalledIn(getSrcServerDirectory())) + { + return ShowWizard; + } + + // data are not copied + if (!areRyzomDataInstalledIn(serverDirectory)) + { + return CopyServerFiles; + } + + // client is not extracted from BNP + if (!isRyzomClientInstalledIn(serverDirectory)) + { + if (QFile::exists(getSrcServerClientBNPFullPath())) + { + return ExtractBnpClient; + } + else + { + QString clientFile = getInstallationDirectory() + "/" + server.clientDownloadFilename; + + // when file is not finished, it has .part extension + if (!QFile::exists(clientFile)) + { + return DownloadClient; + } + + return ExtractDownloadedClient; + } + } + } + + // no default profile + if (profile.id < 0) + { + return CreateProfile; + } + + // migration profile + if (!getSrcServerDirectory().isEmpty() && QFile::exists(getSrcProfileDirectory() + "/client.cfg") && !QFile::exists(QString("%1/%2/client.cfg").arg(getProfileDirectory()).arg(profile.name))) + { + return CopyProfileFiles; + } + + if (!QFile::exists(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/Ryzom.lnk")) + { + // TODO: check they point to getClientFullPath() + return CreateShortcuts; + } + + return Done; +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/configfile.h b/code/ryzom/tools/client/ryzom_installer/src/configfile.h new file mode 100644 index 000000000..32bfb99c6 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/configfile.h @@ -0,0 +1,180 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef CONFIGFILE_H +#define CONFIGFILE_H + +struct CServer +{ + CServer() + { + dataCompressedSize = 0; + dataUncompressedSize = 0; + } + + QString id; + QString name; + QString displayUrl; + QString dataDownloadUrl; + QString dataDownloadFilename; + qint64 dataCompressedSize; + qint64 dataUncompressedSize; + QString clientDownloadUrl; + QString clientDownloadFilename; + QString clientFilename; + QString comments; +}; + +extern const CServer NoServer; + +typedef QVector CServers; + +struct CProfile +{ + CProfile() + { + id = -1; + desktopShortcut = false; + menuShortcut = false; + } + + int id; + QString account; + QString name; + QString server; + QString executable; + QString arguments; + QString comments; + bool desktopShortcut; + bool menuShortcut; +}; + +extern const CProfile NoProfile; + +typedef QVector CProfiles; + +/** + * Config file management and other stuff related to location of files/directories. + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CConfigFile : public QObject +{ + Q_OBJECT + +public: + enum InstallationStep + { + DisplayNoServerError, + ShowWizard, + DownloadData, + ExtractDownloadedData, + DownloadClient, + ExtractDownloadedClient, + CopyServerFiles, + CopyProfileFiles, + ExtractBnpClient, + CreateProfile, + CreateShortcuts, + Done + }; + + CConfigFile(QObject *parent = NULL); + virtual ~CConfigFile(); + + bool load(); + bool load(const QString &filename); + bool save() const; + + static CConfigFile* getInstance(); + + CServers getServers() const { return m_servers; } + void setServers(const CServers &servers) { m_servers = servers; } + + int getServersCount() const; + const CServer& getServer(int i = -1) const; + const CServer& getServer(const QString &id) const; + + CProfiles getProfiles() const { return m_profiles; } + void setProfiles(const CProfiles &profiles) { m_profiles = profiles; } + + int getProfilesCount() const; + CProfile getProfile(int i = -1) const; + void setProfile(int i, const CProfile &profile); + int addProfile(const CProfile &profile); + void removeProfile(int i); + + int getDefaultServer() const; + int getDefaultProfile() const; + + bool isRyzomInstallerConfigured() const; + + QString getInstallationDirectory() const; + void setInstallationDirectory(const QString &directory); + + QString getSrcServerDirectory() const; + void setSrcServerDirectory(const QString &directory); + + QString getProfileDirectory() const; + QString getSrcProfileDirectory() const; + + static bool has64bitsOS(); + + // default directories + static QString getCurrentDirectory(); + static QString getParentDirectory(); + static QString getApplicationDirectory(); + static QString getOldInstallationDirectory(); + static QString getNewInstallationDirectory(); + + bool isRyzomInstalledIn(const QString &directory) const; + bool areRyzomDataInstalledIn(const QString &directory) const; + bool isRyzomClientInstalledIn(const QString &directory) const; + + // installation choices + bool use64BitsClient() const; + void setUse64BitsClient(bool on); + + QString expandVariables(const QString &str); + + QString getClientArch() const; + + QString getClientFullPath() const; + + QString getSrcServerClientBNPFullPath() const; + + InstallationStep getNextStep() const; + +private: + int m_defaultServer; + int m_defaultProfile; + + CServers m_servers; + CProfiles m_profiles; + + QString m_installationDirectory; + QString m_srcDirectory; + bool m_use64BitsClient; + QString m_language; + + QString m_defaultConfigPath; + QString m_configPath; + + static CConfigFile *s_instance; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/downloader.cpp b/code/ryzom/tools/client/ryzom_installer/src/downloader.cpp new file mode 100644 index 000000000..f129416ad --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/downloader.cpp @@ -0,0 +1,394 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "downloader.h" + +#include "nel/misc/system_info.h" +#include "nel/misc/path.h" + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +CDownloader::CDownloader(QObject *parent):QObject(parent), m_manager(NULL), m_reply(NULL), m_timer(NULL), + m_offset(0), m_size(0), m_supportsAcceptRanges(false), m_supportsContentRange(false), + m_downloadAfterHead(false), m_aborted(false), m_file(NULL) +{ + m_manager = new QNetworkAccessManager(this); + m_timer = new QTimer(this); + + connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimeout())); +} + +CDownloader::~CDownloader() +{ + stopTimer(); + closeFile(); +} + +bool CDownloader::getHtmlPageContent(const QString &url) +{ + if (url.isEmpty()) return false; + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::UserAgentHeader, "Ryzom Installer/1.0"); + + QNetworkReply *reply = m_manager->get(request); + + connect(reply, SIGNAL(finished()), SLOT(onHtmlPageFinished())); + + return true; +} + +bool CDownloader::prepareFile(const QString &url, const QString &fullPath) +{ + if (url.isEmpty()) return false; + + m_downloadAfterHead = false; + + emit downloadPrepare(); + + m_fullPath = fullPath; + m_url = url; + + getFileHead(); + + return true; +} + +bool CDownloader::getFile() +{ + if (m_fullPath.isEmpty() || m_url.isEmpty()) + { + qDebug() << "You forget to call prepareFile before"; + + return false; + } + + m_downloadAfterHead = true; + + getFileHead(); + + return true; +} + +bool CDownloader::stop() +{ + if (!m_reply) return false; + + m_reply->abort(); + + return true; +} + +void CDownloader::startTimer() +{ + stopTimer(); + + m_timer->setInterval(5000); + m_timer->setSingleShot(true); + m_timer->start(); +} + +void CDownloader::stopTimer() +{ + if (m_timer->isActive()) m_timer->stop(); +} + +bool CDownloader::openFile() +{ + closeFile(); + + m_file = new QFile(m_fullPath); + + if (m_file->open(QFile::Append)) return true; + + closeFile(); + + return false; +} + +void CDownloader::closeFile() +{ + if (m_file) + { + m_file->close(); + + delete m_file; + m_file = NULL; + } +} + +void CDownloader::getFileHead() +{ + if (m_supportsAcceptRanges) + { + QFileInfo fileInfo(m_fullPath); + + if (fileInfo.exists()) + { + m_offset = fileInfo.size(); + } + else + { + m_offset = 0; + } + + // continue if offset less than size + if (m_offset >= m_size) + { + if (checkDownloadedFile()) + { + // file is already downloaded + emit downloadSuccess(m_size); + } + else + { + // or has wrong size + emit downloadFail(tr("File (%1B) is larger than expected (%2B)").arg(m_offset).arg(m_size)); + } + + return; + } + } + + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0"); + + if (m_supportsAcceptRanges) + { + request.setRawHeader("Range", QString("bytes=%1-").arg(m_offset).toLatin1()); + } + + m_reply = m_manager->head(request); + + connect(m_reply, SIGNAL(finished()), SLOT(onHeadFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onError(QNetworkReply::NetworkError))); + + startTimer(); +} + +void CDownloader::downloadFile() +{ + qint64 freeSpace = NLMISC::CSystemInfo::availableHDSpace(m_fullPath.toUtf8().constData()); + + if (freeSpace < m_size - m_offset) + { + // we have not enough free disk space to continue download + emit downloadFail(tr("You only have %1 bytes left on device, but %2 bytes are required.").arg(freeSpace).arg(m_size - m_offset)); + return; + } + + if (!openFile()) + { + emit downloadFail(tr("Unable to write file")); + return; + } + + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12.388 Version/12.17"); + + if (supportsResume()) + { + request.setRawHeader("Range", QString("bytes=%1-%2").arg(m_offset).arg(m_size-1).toLatin1()); + } + + m_reply = m_manager->get(request); + + connect(m_reply, SIGNAL(finished()), SLOT(onDownloadFinished())); + connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onError(QNetworkReply::NetworkError))); + connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64))); + connect(m_reply, SIGNAL(readyRead()), SLOT(onDownloadRead())); + + emit downloadStart(); + + startTimer(); +} + +bool CDownloader::checkDownloadedFile() +{ + QFileInfo file(m_fullPath); + + return file.size() == m_size && file.lastModified().toUTC() == m_lastModified; +} + +void CDownloader::onTimeout() +{ + qDebug() << "Timeout"; + + emit downloadFail(tr("Timeout")); +} + +void CDownloader::onHtmlPageFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QString html = QString::fromUtf8(reply->readAll()); + + reply->deleteLater(); + + emit htmlPageContent(html); +} + +void CDownloader::onHeadFinished() +{ + stopTimer(); + + int status = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + QString redirection = m_reply->header(QNetworkRequest::LocationHeader).toString(); + + m_size = m_reply->header(QNetworkRequest::ContentLengthHeader).toInt(); + m_lastModified = m_reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toUTC(); + + QString acceptRanges = QString::fromLatin1(m_reply->rawHeader("Accept-Ranges")); + QString contentRange = QString::fromLatin1(m_reply->rawHeader("Content-Range")); + + m_reply->deleteLater(); + m_reply = NULL; + + // redirection + if (status == 302) + { + if (redirection.isEmpty()) + { + emit downloadFail(tr("Redirection URL is not defined")); + return; + } + + // redirection on another server, recheck resume + m_supportsAcceptRanges = false; + m_supportsContentRange = false; + + m_referer = m_url; + + // update real URL + m_url = redirection; + + getFileHead(); + + return; + } + + // we requested without range + else if (status == 200) + { + // update size + emit downloadInit(0, m_size); + + if (!m_supportsAcceptRanges && acceptRanges == "bytes") + { + // server supports resume, part 1 + m_supportsAcceptRanges = true; + + // request range + getFileHead(); + return; + } + + // server doesn't support resume or + // we requested range, but server always returns 200 + // download from the beginning + } + + // we requested with a range + else if (status == 206) + { + // server supports resume + QRegExp regexp("^bytes ([0-9]+)-([0-9]+)/([0-9]+)$"); + + if (m_supportsAcceptRanges && regexp.exactMatch(contentRange)) + { + m_supportsContentRange = true; + m_offset = regexp.cap(1).toLongLong(); + + // when resuming, Content-Length is the size of missing parts to download + m_size = regexp.cap(3).toLongLong(); + + // update offset and size + emit downloadInit(m_offset, m_size); + } + else + { + qDebug() << "Unable to parse"; + } + } + + // other status + else + { + emit downloadFail(tr("Wrong status code: %1").arg(status)); + return; + } + + if (m_downloadAfterHead) + { + if (checkDownloadedFile()) + { + qDebug() << "same date and size"; + } + else + { + downloadFile(); + } + } +} + +void CDownloader::onDownloadFinished() +{ + m_reply->deleteLater(); + m_reply = NULL; + + closeFile(); + + if (m_aborted) + { + m_aborted = false; + emit downloadStop(); + } + else + { + bool ok = NLMISC::CFile::setFileModificationDate(m_fullPath.toUtf8().constData(), m_lastModified.toTime_t()); + + emit downloadSuccess(m_size); + } +} + +void CDownloader::onError(QNetworkReply::NetworkError error) +{ + if (error == QNetworkReply::OperationCanceledError) + { + m_aborted = true; + } + else + { + emit downloadFail(tr("Network error: %1").arg(error)); + } +} + +void CDownloader::onDownloadProgress(qint64 current, qint64 total) +{ + stopTimer(); + + emit downloadProgress(m_offset + current); +} + +void CDownloader::onDownloadRead() +{ + if (m_file) m_file->write(m_reply->readAll()); +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/downloader.h b/code/ryzom/tools/client/ryzom_installer/src/downloader.h new file mode 100644 index 000000000..7d9090700 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/downloader.h @@ -0,0 +1,110 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef DOWNLOADER_H +#define DOWNLOADER_H + +/** + * Files downloader, please note that only one file can be downloaded at once. + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CDownloader : public QObject +{ + Q_OBJECT + +public: + CDownloader(QObject *parent); + virtual ~CDownloader(); + + bool getHtmlPageContent(const QString &url); + + bool prepareFile(const QString &url, const QString &fullPath); + bool getFile(); + bool stop(); + + bool supportsResume() const { return m_supportsAcceptRanges && m_supportsContentRange; } + + bool isDownloading() const { return m_file != NULL; } + +signals: + // emitted when requesting real URL + void downloadPrepare(); + + // emitted when we got the initial (local) and total (remote) size of file + void downloadInit(qint64 current, qint64 total); + + // emitted when we begin to download + void downloadStart(); + + // emitted when the download stopped + void downloadStop(); + + // emittd when downloading + void downloadProgress(qint64 current); + + // emitted when the whole file is downloaded + void downloadSuccess(qint64 total); + + // emitted when an error occurs + void downloadFail(const QString &error); + + void htmlPageContent(const QString &html); + +private slots: + void onTimeout(); + void onHtmlPageFinished(); + void onHeadFinished(); + void onDownloadFinished(); + void onError(QNetworkReply::NetworkError error); + void onDownloadProgress(qint64 current, qint64 total); + void onDownloadRead(); + +protected: + void startTimer(); + void stopTimer(); + + bool openFile(); + void closeFile(); + + void getFileHead(); + void downloadFile(); + + bool checkDownloadedFile(); + + QNetworkAccessManager *m_manager; + QNetworkReply *m_reply; + QTimer *m_timer; + + QString m_url; + QString m_referer; + QString m_fullPath; + + qint64 m_offset; + qint64 m_size; + QDateTime m_lastModified; + + bool m_supportsAcceptRanges; + bool m_supportsContentRange; + + bool m_downloadAfterHead; + bool m_aborted; + + QFile *m_file; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/main.cpp b/code/ryzom/tools/client/ryzom_installer/src/main.cpp new file mode 100644 index 000000000..c78021ae5 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/main.cpp @@ -0,0 +1,108 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "mainwindow.h" +#include "configfile.h" +#include "wizarddialog.h" + +#include "nel/misc/path.h" +#include "nel/misc/ucstring.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef QT_STATICPLUGIN + +#include + +#if defined(Q_OS_WIN32) + Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) +#elif defined(Q_OS_MAC) + Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) +#elif defined(Q_OS_UNIX) + Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) + Q_IMPORT_PLUGIN(QXcbGlxIntegrationPlugin) +#endif + + Q_IMPORT_PLUGIN(QICOPlugin) + +#endif + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +int main(int argc, char *argv[]) +{ +#if defined(_MSC_VER) && defined(_DEBUG) +// _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + + NLMISC::CApplicationContext appContext; + + QApplication app(argc, argv); + + QApplication::setApplicationName("Ryzom"); + QApplication::setApplicationVersion(RYZOM_VERSION); + QApplication::setWindowIcon(QIcon(":/icons/ryzom.ico")); + + QLocale locale = QLocale::system(); + + // load application translations + QTranslator localTranslator; + if (localTranslator.load(locale, "ryzom_installer", "_", "translations")) + { + QApplication::installTranslator(&localTranslator); + } + + // load Qt default translations + QTranslator qtTranslator; + if (qtTranslator.load(locale, "qt", "_", "translations")) + { + QApplication::installTranslator(&qtTranslator); + } + + // instanciate ConfigFile + CConfigFile config; + CConfigFile::InstallationStep step = config.load() ? config.getNextStep():CConfigFile::DisplayNoServerError; + + if (step == CConfigFile::DisplayNoServerError) + { + // display error + return 1; + } + + bool displayMainWindow = true; + + if (step == CConfigFile::ShowWizard) + { + CWizardDialog dialog; + + if (!dialog.exec()) displayMainWindow = false; + } + + if (displayMainWindow) + { + CMainWindow mainWindow; + mainWindow.show(); + + return QApplication::exec(); + } + + return 0; +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp new file mode 100644 index 000000000..d316bc6c9 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp @@ -0,0 +1,397 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "mainwindow.h" +#include "downloader.h" +#include "archive.h" +#include "wizarddialog.h" +#include "profilesdialog.h" +#include "configfile.h" +#include "config.h" + +#include "seven_zip.h" + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) +#include +#include +#endif + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +CMainWindow::CMainWindow():QMainWindow(), m_archive(NULL), m_statusLabel(NULL) +{ + setupUi(this); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button = new QWinTaskbarButton(this); +#endif + + connect(resumeButton, SIGNAL(clicked()), SLOT(onResumeClicked())); + connect(stopButton, SIGNAL(clicked()), SLOT(onStopClicked())); + + // downloader + m_downloader = new CDownloader(this); + + connect(m_downloader, SIGNAL(downloadPrepare()), SLOT(onDownloadPrepare())); + connect(m_downloader, SIGNAL(downloadInit(qint64, qint64)), SLOT(onDownloadInit(qint64, qint64))); + connect(m_downloader, SIGNAL(downloadStart()), SLOT(onDownloadStart())); + connect(m_downloader, SIGNAL(downloadStop()), SLOT(onDownloadStop())); + connect(m_downloader, SIGNAL(downloadProgress(qint64)), SLOT(onDownloadProgress(qint64))); + connect(m_downloader, SIGNAL(downloadSuccess(qint64)), SLOT(onDownloadSuccess(qint64))); + connect(m_downloader, SIGNAL(downloadFail(QString)), SLOT(onDownloadFail(QString))); + connect(m_downloader, SIGNAL(htmlPageContent(QString)), SLOT(onHtmlPageContent(QString))); + + // archive + m_archive = new CArchive(this); + + connect(m_archive, SIGNAL(extractPrepare()), SLOT(onExtractPrepare())); + connect(m_archive, SIGNAL(extractInit(qint64, qint64)), SLOT(onExtractInit(qint64, qint64))); + connect(m_archive, SIGNAL(extractStart()), SLOT(onExtractStart())); + connect(m_archive, SIGNAL(extractStop()), SLOT(onExtractStop())); + connect(m_archive, SIGNAL(extractProgress(qint64, QString)), SLOT(onExtractProgress(qint64, QString))); + connect(m_archive, SIGNAL(extractSuccess(qint64)), SLOT(onExtractSuccess(qint64))); + connect(m_archive, SIGNAL(extractFail(QString)), SLOT(onExtractFail(QString))); + + connect(actionProfiles, SIGNAL(triggered()), SLOT(onProfiles())); + + connect(actionAboutQt, SIGNAL(triggered()), SLOT(onAboutQt())); + connect(actionAbout, SIGNAL(triggered()), SLOT(onAbout())); + + m_statusLabel = new QLabel(); + + statusBar()->addWidget(m_statusLabel); + +// setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); +} + +CMainWindow::~CMainWindow() +{ +} + +void CMainWindow::processNextStep() +{ + CConfigFile *config = CConfigFile::getInstance(); + + // default server + const CServer &server = config->getServer(); + + // default profile + const CProfile &configuration = config->getProfile(); + + switch(CConfigFile::getInstance()->getNextStep()) + { + case CConfigFile::DisplayNoServerError: + break; + + case CConfigFile::ShowWizard: + break; + + case CConfigFile::DownloadData: + displayProgressBar(); + m_downloader->prepareFile(config->expandVariables(server.dataDownloadUrl), config->getInstallationDirectory() + "/" + config->expandVariables(server.dataDownloadFilename) + ".part"); + break; + + case CConfigFile::ExtractDownloadedData: + displayProgressBar(); + break; + + case CConfigFile::DownloadClient: + displayProgressBar(); + m_downloader->prepareFile(config->expandVariables(server.clientDownloadUrl), config->getInstallationDirectory() + "/" + config->expandVariables(server.clientDownloadFilename) + ".part"); + break; + + case CConfigFile::ExtractDownloadedClient: + displayProgressBar(); + break; + + case CConfigFile::CopyServerFiles: + displayProgressBar(); + m_archive->copyServerFiles(config->getSrcServerDirectory(), config->getInstallationDirectory() + "/" + server.id); + break; + + case CConfigFile::CopyProfileFiles: + displayProgressBar(); + m_archive->copyProfileFiles(config->getSrcProfileDirectory(), config->getProfileDirectory() + "/0"); + break; + + case CConfigFile::ExtractBnpClient: + displayProgressBar(); + m_archive->extract(config->getSrcServerClientBNPFullPath(), config->getInstallationDirectory() + "/" + server.id); + break; + + case CConfigFile::CreateProfile: + displayProgressBar(); + break; + + case CConfigFile::CreateShortcuts: + displayProgressBar(); + break; + + default: + // cases already managed in main.cpp + displayConfigurationsChoices(); + break; + } + + m_downloader->getHtmlPageContent(config->expandVariables(server.displayUrl)); +} + +void CMainWindow::displayProgressBar() +{ + downloadFrame->setVisible(true); + configurationFrame->setVisible(false); + + resumeButton->setVisible(true); + stopButton->setVisible(false); +} + +void CMainWindow::displayConfigurationsChoices() +{ + downloadFrame->setVisible(false); + configurationFrame->setVisible(true); +} + +void CMainWindow::showEvent(QShowEvent *e) +{ +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->setWindow(windowHandle()); +#endif + + e->accept(); + + processNextStep(); +} + +void CMainWindow::closeEvent(QCloseEvent *e) +{ + hide(); + + e->accept(); +} + +void CMainWindow::onResumeClicked() +{ + m_downloader->getFile(); +} + +void CMainWindow::onStopClicked() +{ + if (m_downloader->isDownloading()) + { + if (!m_downloader->supportsResume()) + { + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Confirmation"), tr("Warning, this server doesn't support resume! If you stop download now, you won't be able to resume it later.\nAre you sure to abort download?")); + + if (res != QMessageBox::Yes) return; + } + + m_downloader->stop(); + } + else + { + m_archive->stop(); + } +} + +void CMainWindow::onProfiles() +{ + CProfilesDialog dialog; + + dialog.exec(); +} + +void CMainWindow::onAbout() +{ + QString br("
"); + + QMessageBox::about(this, + tr("About %1").arg("Ryzom Installer"), + QString("Ryzom Installer") + QApplication::applicationVersion() + br + + tr("Program to install, download and manage Ryzom configurations.") + + br+br+ + tr("Author: %1").arg("Cedric 'Kervala' OCHS") + br + + tr("Copyright: %1").arg(COPYRIGHT) + br + + tr("Support: %1").arg("Ryzom Core on Bitbucket")); +} + +void CMainWindow::onAboutQt() +{ + QMessageBox::aboutQt(this); +} + +void CMainWindow::onDownloadPrepare() +{ + progressBar->setFormat(tr("%p% (%v/%m KiB)")); + + progressBar->setMinimum(0); + progressBar->setMaximum(0); + progressBar->setValue(0); + + resumeButton->setVisible(false); + stopButton->setVisible(false); +} + +void CMainWindow::onDownloadInit(qint64 current, qint64 total) +{ + resumeButton->setVisible(true); + stopButton->setVisible(false); + + progressBar->setMinimum(0); + progressBar->setMaximum(total / 1024); + progressBar->setValue(current / 1024); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->setMinimum(0); + m_button->progress()->setMaximum(total / 1024); + m_button->progress()->setValue(current / 1024); +#endif +} + +void CMainWindow::onDownloadStart() +{ + resumeButton->setVisible(false); + stopButton->setVisible(true); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->show(); +#endif +} + +void CMainWindow::onDownloadStop() +{ + resumeButton->setVisible(true); + stopButton->setVisible(false); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->hide(); +#endif +} + +void CMainWindow::onDownloadProgress(qint64 current) +{ + progressBar->setValue(current / 1024); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->setValue(current / 1024); +#endif +} + +void CMainWindow::onDownloadSuccess(qint64 total) +{ + progressBar->setValue(total / 1024); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->hide(); +#endif + + resumeButton->setVisible(false); + stopButton->setVisible(false); +} + +void CMainWindow::onDownloadFail(const QString &error) +{ + resumeButton->setVisible(true); + stopButton->setVisible(false); +} + +void CMainWindow::onHtmlPageContent(const QString &html) +{ + htmlTextEdit->setHtml(html); +} + +void CMainWindow::onExtractPrepare() +{ + progressBar->setFormat("%p%"); + + progressBar->setMinimum(0); + progressBar->setMaximum(0); + progressBar->setValue(0); + + resumeButton->setVisible(false); + stopButton->setVisible(false); +} + +void CMainWindow::onExtractInit(qint64 current, qint64 total) +{ + resumeButton->setVisible(true); + stopButton->setVisible(false); + + progressBar->setMinimum(0); + progressBar->setMaximum(total / 1024); + progressBar->setValue(current / 1024); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->setMinimum(0); + m_button->progress()->setMaximum(total / 1024); + m_button->progress()->setValue(current / 1024); +#endif +} + +void CMainWindow::onExtractStart() +{ + resumeButton->setVisible(false); + stopButton->setVisible(true); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->show(); +#endif +} + +void CMainWindow::onExtractStop() +{ + resumeButton->setVisible(true); + stopButton->setVisible(false); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->hide(); +#endif +} + +void CMainWindow::onExtractProgress(qint64 current, const QString &filename) +{ + m_statusLabel->setText(tr("Extracting %1...").arg(filename)); + + progressBar->setValue(current / 1024); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->setValue(current / 1024); +#endif +} + +void CMainWindow::onExtractSuccess(qint64 total) +{ + m_statusLabel->setText(tr("Extraction done")); + + progressBar->setValue(total / 1024); + +#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB) + m_button->progress()->hide(); +#endif + + resumeButton->setVisible(false); + stopButton->setVisible(false); + + processNextStep(); +} + +void CMainWindow::onExtractFail(const QString &error) +{ + resumeButton->setVisible(true); + stopButton->setVisible(false); +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/mainwindow.h b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.h new file mode 100644 index 000000000..7e03f9a80 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.h @@ -0,0 +1,82 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "ui_mainwindow.h" + +class QWinTaskbarButton; +class CDownloader; +class CArchive; + +/** + * Main window + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CMainWindow : public QMainWindow, public Ui::MainWindow +{ + Q_OBJECT + +public: + CMainWindow(); + virtual ~CMainWindow(); + +public slots: + void onResumeClicked(); + void onStopClicked(); + + void onProfiles(); + void onAbout(); + void onAboutQt(); + + void onDownloadPrepare(); + void onDownloadInit(qint64 current, qint64 total); + void onDownloadStart(); + void onDownloadStop(); + void onDownloadProgress(qint64 current); + void onDownloadSuccess(qint64 total); + void onDownloadFail(const QString &error); + + void onHtmlPageContent(const QString &html); + + void onExtractPrepare(); + void onExtractInit(qint64 current, qint64 total); + void onExtractStart(); + void onExtractStop(); + void onExtractProgress(qint64 current, const QString &filename); + void onExtractSuccess(qint64 total); + void onExtractFail(const QString &error); + +protected: + void showEvent(QShowEvent *e); + void closeEvent(QCloseEvent *e); + + void processNextStep(); + + void displayProgressBar(); + void displayConfigurationsChoices(); + + QWinTaskbarButton *m_button; + CDownloader *m_downloader; + CArchive *m_archive; + + QLabel *m_statusLabel; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.cpp new file mode 100644 index 000000000..ff42f12ec --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.cpp @@ -0,0 +1,207 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "profilesdialog.h" +#include "profilesmodel.h" +#include "serversmodel.h" + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +CProfilesDialog::CProfilesDialog():QDialog(), m_currentProfileIndex(-1) +{ + setupUi(this); + + connect(addButton, SIGNAL(clicked()), SLOT(onAddProfile())); + connect(deleteButton, SIGNAL(clicked()), SLOT(onDeleteProfile())); + connect(profilesListView, SIGNAL(clicked(QModelIndex)), SLOT(onProfileClicked(QModelIndex))); + connect(executableBrowseButton, SIGNAL(clicked()), SLOT(onExecutableBrowseClicked())); + + m_model = new CProfilesModel(this); + m_serversModel = new CServersModel(this); + + profilesListView->setModel(m_model); + serverComboBox->setModel(m_serversModel); + + int index = m_model->getIndexFromProfileID(CConfigFile::getInstance()->getDefaultProfile()); + + profilesListView->setCurrentIndex(m_model->index(index, 0)); + displayProfile(index); +} + +CProfilesDialog::~CProfilesDialog() +{ +} + +void CProfilesDialog::accept() +{ + saveProfile(m_currentProfileIndex); + + m_model->save(); + + QDialog::accept(); +} + +void CProfilesDialog::onAddProfile() +{ +} + +void CProfilesDialog::onDeleteProfile() +{ + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Confirmation"), tr("You're going to delete a profile, files won't be deleted and you'll have to do that manually.\nAre you sure to delete this profile?")); + + if (res != QMessageBox::Yes) return; + + QModelIndex index = profilesListView->currentIndex(); + + deleteProfile(index.row()); +} + +void CProfilesDialog::onProfileClicked(const QModelIndex &index) +{ + qDebug() << "clicked on" << index; + + displayProfile(index.row()); +} + +void CProfilesDialog::displayProfile(int index) +{ + bool enabled = index > -1; + + profileIdLabel->setEnabled(enabled); + accountEdit->setEnabled(enabled); + nameEdit->setEnabled(enabled); + serverComboBox->setEnabled(enabled); + argumentsEdit->setEnabled(enabled); + commentsEdit->setEnabled(enabled); + + if (index < 0) return; + + saveProfile(m_currentProfileIndex); + + const CProfile &profile = m_model->getProfiles()[index]; + + // update all widgets with content of profile + profileIdLabel->setText(QString::number(profile.id)); + accountEdit->setText(profile.account); + nameEdit->setText(profile.name); + serverComboBox->setCurrentIndex(m_serversModel->getIndexFromServerID(profile.server)); + executablePathLabel->setText(QFileInfo(profile.executable).fileName()); + argumentsEdit->setText(profile.arguments); + commentsEdit->setPlainText(profile.comments); + directoryPathLabel->setText(CConfigFile::getInstance()->getProfileDirectory()); + desktopShortcutCheckBox->setChecked(profile.desktopShortcut); + menuShortcutCheckBox->setChecked(profile.menuShortcut); + + updateExecutableVersion(index); + + m_currentProfileIndex = index; +} + +void CProfilesDialog::saveProfile(int index) +{ + if (index < 0) return; + + CProfile &profile = m_model->getProfiles()[index]; + + profile.account = accountEdit->text(); + profile.name = nameEdit->text(); + profile.server = m_serversModel->getServerIDFromIndex(serverComboBox->currentIndex()); + profile.arguments = argumentsEdit->text(); + profile.comments = commentsEdit->toPlainText(); + profile.desktopShortcut = desktopShortcutCheckBox->isChecked(); + profile.menuShortcut = menuShortcutCheckBox->isChecked(); +} + +void CProfilesDialog::deleteProfile(int index) +{ + if (index < 0) return; + + m_model->removeRow(index); +} + +void CProfilesDialog::addProfile() +{ + // TODO: browse all folders in AppData/Roaming/Ryzom +} + +void CProfilesDialog::updateExecutableVersion(int index) +{ + if (index < 0) return; + + const CProfile &profile = m_model->getProfiles()[index]; + + QString executable = profile.executable; + + // file empty, use default one + if (executable.isEmpty()) + { + executable = CConfigFile::getInstance()->getInstallationDirectory() + "/" + profile.server + "/"; + +#if defined(Q_OS_WIN32) + executable += "ryzom_client_r.exe"; +#elif defined(Q_OS_APPLE) + executable += "Ryzom.app/Contents/MacOS/Ryzom"; +#else + executable += "ryzom_client"; +#endif + } + + // file doesn't exist + if (!QFile::exists(executable)) return; + + // launch executable with --version argument + QProcess process; + process.setProcessChannelMode(QProcess::MergedChannels); + process.start(executable, QStringList() << "--version", QIODevice::ReadWrite); + + if (!process.waitForStarted()) return; + + QByteArray data; + + // read all output + while (process.waitForReadyRead()) data.append(process.readAll()); + + // convert output to string + QString versionString = QString::fromUtf8(data); + + // parse version from output + QRegExp reg("([A-Za-z0-1_.]+) ((DEV|FV) ([0-9.]+))"); + + if (reg.indexIn(versionString) > -1) + { + executableVersionLabel->setText(reg.cap(2)); + } +} + +void CProfilesDialog::onExecutableBrowseClicked() +{ + if (m_currentProfileIndex < 0) return; + + CProfile &profile = m_model->getProfiles()[m_currentProfileIndex]; + + QString file = QFileDialog::getOpenFileName(this, tr("Please choose Ryzom client executable to launch"), profile.executable, tr("Executables (*.exe)")); + + if (file.isEmpty()) return; + + profile.executable = file; + + executablePathLabel->setText(QFileInfo(profile.executable).fileName()); + + updateExecutableVersion(m_currentProfileIndex); +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.h b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.h new file mode 100644 index 000000000..231ab554a --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.h @@ -0,0 +1,62 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef PROFILESDIALOG_H +#define PROFILESDIALOG_H + +#include "ui_profiles.h" + +class CProfilesModel; +class CServersModel; + +/** + * Dialog displayed when editing existing profiles. + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CProfilesDialog : public QDialog, public Ui::ProfilesDialog +{ + Q_OBJECT + +public: + CProfilesDialog(); + virtual ~CProfilesDialog(); + +private slots: + void accept(); + + void onAddProfile(); + void onDeleteProfile(); + void onProfileClicked(const QModelIndex &index); + + void displayProfile(int index); + void saveProfile(int index); + void deleteProfile(int index); + void addProfile(); + + void updateExecutableVersion(int index); + + void onExecutableBrowseClicked(); + +private: + CProfilesModel *m_model; + CServersModel *m_serversModel; + + int m_currentProfileIndex; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.cpp b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.cpp new file mode 100644 index 000000000..64f15c5d5 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.cpp @@ -0,0 +1,66 @@ +#include "profilesmodel.h" + +CProfilesModel::CProfilesModel(QObject *parent):QAbstractListModel(parent) +{ + m_profiles = CConfigFile::getInstance()->getProfiles(); +} + +CProfilesModel::CProfilesModel(const CProfiles &profiles, QObject *parent):QAbstractListModel(parent), m_profiles(profiles) +{ +} + +CProfilesModel::~CProfilesModel() +{ +} + +int CProfilesModel::rowCount(const QModelIndex &parent) const +{ + return m_profiles.size(); +} + +QVariant CProfilesModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole) return QVariant(); + + const CProfile &profile = m_profiles.at(index.row()); + + return QString("%1 (#%2)").arg(profile.name).arg(profile.id); +} + +bool CProfilesModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row < 0) return false; + + beginRemoveRows(parent, row, row + count - 1); + + m_profiles.removeAt(row); + + endRemoveRows(); + + return true; +} + +bool CProfilesModel::save() const +{ + CConfigFile::getInstance()->setProfiles(m_profiles); + CConfigFile::getInstance()->save(); + + return true; +} + +int CProfilesModel::getIndexFromProfileID(int profileId) const +{ + for(int i = 0; i < m_profiles.size(); ++i) + { + if (m_profiles[i].id == profileId) return i; + } + + return -1; +} + +int CProfilesModel::getProfileIDFromIndex(int index) const +{ + if (index < 0 || index >= m_profiles.size()) return -1; + + return m_profiles[index].id; +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.h b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.h new file mode 100644 index 000000000..ea02f618c --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.h @@ -0,0 +1,35 @@ +#ifndef PROFILESMODEL_H +#define PROFILESMODEL_H + +#include "configfile.h" + +/** + * Profiles model + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CProfilesModel : public QAbstractListModel +{ +public: + CProfilesModel(QObject *parent); + CProfilesModel(const CProfiles &profiles, QObject *parent); + virtual ~CProfilesModel(); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex()); + + CProfiles& getProfiles() { return m_profiles; } + + bool save() const; + + int getIndexFromProfileID(int profileId) const; + int getProfileIDFromIndex(int index) const; + +private: + CProfiles m_profiles; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/qzip.cpp b/code/ryzom/tools/client/ryzom_installer/src/qzip.cpp new file mode 100644 index 000000000..7372a0e82 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/qzip.cpp @@ -0,0 +1,1260 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "stdpch.h" + +#include "qzipreader.h" +#include "qzipwriter.h" + +#include + +#if defined(Q_OS_WIN) +# undef S_IFREG +# define S_IFREG 0100000 +# ifndef S_IFDIR +# define S_IFDIR 0040000 +# endif +# ifndef S_ISDIR +# define S_ISDIR(x) ((x) & S_IFDIR) > 0 +# endif +# ifndef S_ISREG +# define S_ISREG(x) ((x) & 0170000) == S_IFREG +# endif +# define S_IFLNK 020000 +# define S_ISLNK(x) ((x) & S_IFLNK) > 0 +# ifndef S_IRUSR +# define S_IRUSR 0400 +# endif +# ifndef S_IWUSR +# define S_IWUSR 0200 +# endif +# ifndef S_IXUSR +# define S_IXUSR 0100 +# endif +# define S_IRGRP 0040 +# define S_IWGRP 0020 +# define S_IXGRP 0010 +# define S_IROTH 0004 +# define S_IWOTH 0002 +# define S_IXOTH 0001 +#else +# include +#endif + +#if 0 +#define ZDEBUG qDebug +#else +#define ZDEBUG if (0) qDebug +#endif + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +QT_BEGIN_NAMESPACE + +static inline uint readUInt(const uchar *data) +{ + return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24); +} + +static inline ushort readUShort(const uchar *data) +{ + return (data[0]) + (data[1]<<8); +} + +static inline void writeUInt(uchar *data, uint i) +{ + data[0] = i & 0xff; + data[1] = (i>>8) & 0xff; + data[2] = (i>>16) & 0xff; + data[3] = (i>>24) & 0xff; +} + +static inline void writeUShort(uchar *data, ushort i) +{ + data[0] = i & 0xff; + data[1] = (i>>8) & 0xff; +} + +static inline void copyUInt(uchar *dest, const uchar *src) +{ + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = src[3]; +} + +static inline void copyUShort(uchar *dest, const uchar *src) +{ + dest[0] = src[0]; + dest[1] = src[1]; +} + +static void writeMSDosDate(uchar *dest, const QDateTime& dt) +{ + if (dt.isValid()) { + quint16 time = + (dt.time().hour() << 11) // 5 bit hour + | (dt.time().minute() << 5) // 6 bit minute + | (dt.time().second() >> 1); // 5 bit double seconds + + dest[0] = time & 0xff; + dest[1] = time >> 8; + + quint16 date = + ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based + | (dt.date().month() << 5) // 4 bit month + | (dt.date().day()); // 5 bit day + + dest[2] = char(date); + dest[3] = char(date >> 8); + } else { + dest[0] = 0; + dest[1] = 0; + dest[2] = 0; + dest[3] = 0; + } +} + +static quint32 permissionsToMode(QFile::Permissions perms) +{ + quint32 mode = 0; + if (perms & QFile::ReadOwner) + mode |= S_IRUSR; + if (perms & QFile::WriteOwner) + mode |= S_IWUSR; + if (perms & QFile::ExeOwner) + mode |= S_IXUSR; + if (perms & QFile::ReadUser) + mode |= S_IRUSR; + if (perms & QFile::WriteUser) + mode |= S_IWUSR; + if (perms & QFile::ExeUser) + mode |= S_IXUSR; + if (perms & QFile::ReadGroup) + mode |= S_IRGRP; + if (perms & QFile::WriteGroup) + mode |= S_IWGRP; + if (perms & QFile::ExeGroup) + mode |= S_IXGRP; + if (perms & QFile::ReadOther) + mode |= S_IROTH; + if (perms & QFile::WriteOther) + mode |= S_IWOTH; + if (perms & QFile::ExeOther) + mode |= S_IXOTH; + return mode; +} + +static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + if ((uLong)stream.avail_in != sourceLen) + return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) + return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit2(&stream, -MAX_WBITS); + if (err != Z_OK) + return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} + +static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +static QFile::Permissions modeToPermissions(quint32 mode) +{ + QFile::Permissions ret; + if (mode & S_IRUSR) + ret |= QFile::ReadOwner; + if (mode & S_IWUSR) + ret |= QFile::WriteOwner; + if (mode & S_IXUSR) + ret |= QFile::ExeOwner; + if (mode & S_IRUSR) + ret |= QFile::ReadUser; + if (mode & S_IWUSR) + ret |= QFile::WriteUser; + if (mode & S_IXUSR) + ret |= QFile::ExeUser; + if (mode & S_IRGRP) + ret |= QFile::ReadGroup; + if (mode & S_IWGRP) + ret |= QFile::WriteGroup; + if (mode & S_IXGRP) + ret |= QFile::ExeGroup; + if (mode & S_IROTH) + ret |= QFile::ReadOther; + if (mode & S_IWOTH) + ret |= QFile::WriteOther; + if (mode & S_IXOTH) + ret |= QFile::ExeOther; + return ret; +} + +static QDateTime readMSDosDate(const uchar *src) +{ + uint dosDate = readUInt(src); + quint64 uDate; + uDate = (quint64)(dosDate >> 16); + uint tm_mday = (uDate & 0x1f); + uint tm_mon = ((uDate & 0x1E0) >> 5); + uint tm_year = (((uDate & 0x0FE00) >> 9) + 1980); + uint tm_hour = ((dosDate & 0xF800) >> 11); + uint tm_min = ((dosDate & 0x7E0) >> 5); + uint tm_sec = ((dosDate & 0x1f) << 1); + + return QDateTime(QDate(tm_year, tm_mon, tm_mday), QTime(tm_hour, tm_min, tm_sec)); +} + +struct LocalFileHeader +{ + uchar signature[4]; // 0x04034b50 + uchar version_needed[2]; + uchar general_purpose_bits[2]; + uchar compression_method[2]; + uchar last_mod_file[4]; + uchar crc_32[4]; + uchar compressed_size[4]; + uchar uncompressed_size[4]; + uchar file_name_length[2]; + uchar extra_field_length[2]; +}; + +struct DataDescriptor +{ + uchar crc_32[4]; + uchar compressed_size[4]; + uchar uncompressed_size[4]; +}; + +struct CentralFileHeader +{ + uchar signature[4]; // 0x02014b50 + uchar version_made[2]; + uchar version_needed[2]; + uchar general_purpose_bits[2]; + uchar compression_method[2]; + uchar last_mod_file[4]; + uchar crc_32[4]; + uchar compressed_size[4]; + uchar uncompressed_size[4]; + uchar file_name_length[2]; + uchar extra_field_length[2]; + uchar file_comment_length[2]; + uchar disk_start[2]; + uchar internal_file_attributes[2]; + uchar external_file_attributes[4]; + uchar offset_local_header[4]; + LocalFileHeader toLocalHeader() const; +}; + +struct EndOfDirectory +{ + uchar signature[4]; // 0x06054b50 + uchar this_disk[2]; + uchar start_of_directory_disk[2]; + uchar num_dir_entries_this_disk[2]; + uchar num_dir_entries[2]; + uchar directory_size[4]; + uchar dir_start_offset[4]; + uchar comment_length[2]; +}; + +struct FileHeader +{ + CentralFileHeader h; + QByteArray file_name; + QByteArray extra_field; + QByteArray file_comment; +}; + +QZipReader::FileInfo::FileInfo() + : isDir(false), isFile(false), isSymLink(false), crc32(0), size(0) +{ +} + +QZipReader::FileInfo::~FileInfo() +{ +} + +QZipReader::FileInfo::FileInfo(const FileInfo &other) +{ + operator=(other); +} + +QZipReader::FileInfo& QZipReader::FileInfo::operator=(const FileInfo &other) +{ + filePath = other.filePath; + isDir = other.isDir; + isFile = other.isFile; + isSymLink = other.isSymLink; + permissions = other.permissions; + crc32 = other.crc32; + size = other.size; + lastModified = other.lastModified; + return *this; +} + +bool QZipReader::FileInfo::isValid() const +{ + return isDir || isFile || isSymLink; +} + +class QZipPrivate +{ +public: + QZipPrivate(QIODevice *device, bool ownDev) + : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0) + { + } + + ~QZipPrivate() + { + if (ownDevice) + delete device; + } + + void fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const; + + QIODevice *device; + bool ownDevice; + bool dirtyFileTree; + QList fileHeaders; + QByteArray comment; + uint start_of_directory; +}; + +void QZipPrivate::fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const +{ + FileHeader header = fileHeaders.at(index); + fileInfo.filePath = QString::fromLocal8Bit(header.file_name); + const quint32 mode = (qFromLittleEndian(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF; + fileInfo.isDir = S_ISDIR(mode); + fileInfo.isFile = S_ISREG(mode); + fileInfo.isSymLink = S_ISLNK(mode); + fileInfo.permissions = modeToPermissions(mode); + fileInfo.crc32 = readUInt(header.h.crc_32); + fileInfo.size = readUInt(header.h.uncompressed_size); + fileInfo.lastModified = readMSDosDate(header.h.last_mod_file); +} + +class QZipReaderPrivate : public QZipPrivate +{ +public: + QZipReaderPrivate(QIODevice *device, bool ownDev) + : QZipPrivate(device, ownDev), status(QZipReader::NoError) + { + } + + void scanFiles(); + + QZipReader::Status status; +}; + +class QZipWriterPrivate : public QZipPrivate +{ +public: + QZipWriterPrivate(QIODevice *device, bool ownDev) + : QZipPrivate(device, ownDev), + status(QZipWriter::NoError), + permissions(QFile::ReadOwner | QFile::WriteOwner), + compressionPolicy(QZipWriter::AlwaysCompress) + { + } + + QZipWriter::Status status; + QFile::Permissions permissions; + QZipWriter::CompressionPolicy compressionPolicy; + + enum EntryType { Directory, File, Symlink }; + + void addEntry(EntryType type, const QString &fileName, const QByteArray &contents); +}; + +LocalFileHeader CentralFileHeader::toLocalHeader() const +{ + LocalFileHeader h; + writeUInt(h.signature, 0x04034b50); + copyUShort(h.version_needed, version_needed); + copyUShort(h.general_purpose_bits, general_purpose_bits); + copyUShort(h.compression_method, compression_method); + copyUInt(h.last_mod_file, last_mod_file); + copyUInt(h.crc_32, crc_32); + copyUInt(h.compressed_size, compressed_size); + copyUInt(h.uncompressed_size, uncompressed_size); + copyUShort(h.file_name_length, file_name_length); + copyUShort(h.extra_field_length, extra_field_length); + return h; +} + +void QZipReaderPrivate::scanFiles() +{ + if (!dirtyFileTree) + return; + + if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) { + status = QZipReader::FileOpenError; + return; + } + + if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files. + status = QZipReader::FileReadError; + return; + } + + dirtyFileTree = false; + uchar tmp[4]; + device->read((char *)tmp, 4); + if (readUInt(tmp) != 0x04034b50) { + qWarning() << "QZip: not a zip file!"; + return; + } + + // find EndOfDirectory header + int i = 0; + int start_of_directory = -1; + int num_dir_entries = 0; + EndOfDirectory eod; + while (start_of_directory == -1) { + int pos = device->size() - (int)sizeof(EndOfDirectory) - i; + if (pos < 0 || i > 65535) { + qWarning() << "QZip: EndOfDirectory not found"; + return; + } + + device->seek(pos); + device->read((char *)&eod, sizeof(EndOfDirectory)); + if (readUInt(eod.signature) == 0x06054b50) + break; + ++i; + } + + // have the eod + start_of_directory = readUInt(eod.dir_start_offset); + num_dir_entries = readUShort(eod.num_dir_entries); + ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries); + int comment_length = readUShort(eod.comment_length); + if (comment_length != i) + qWarning() << "QZip: failed to parse zip file."; + comment = device->read(qMin(comment_length, i)); + + + device->seek(start_of_directory); + for (i = 0; i < num_dir_entries; ++i) { + FileHeader header; + int read = device->read((char *) &header.h, sizeof(CentralFileHeader)); + if (read < (int)sizeof(CentralFileHeader)) { + qWarning() << "QZip: Failed to read complete header, index may be incomplete"; + break; + } + if (readUInt(header.h.signature) != 0x02014b50) { + qWarning() << "QZip: invalid header signature, index may be incomplete"; + break; + } + + int l = readUShort(header.h.file_name_length); + header.file_name = device->read(l); + if (header.file_name.length() != l) { + qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete"; + break; + } + l = readUShort(header.h.extra_field_length); + header.extra_field = device->read(l); + if (header.extra_field.length() != l) { + qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete"; + break; + } + l = readUShort(header.h.file_comment_length); + header.file_comment = device->read(l); + if (header.file_comment.length() != l) { + qWarning() << "QZip: Failed to read read file comment, index may be incomplete"; + break; + } + + ZDEBUG("found file '%s'", header.file_name.data()); + fileHeaders.append(header); + } +} + +void QZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/) +{ +#ifndef NDEBUG + static const char *entryTypes[] = { + "directory", + "file ", + "symlink " }; + ZDEBUG() << "adding" << entryTypes[type] <<":" << fileName.toUtf8().data() << (type == 2 ? (" -> " + contents).constData() : ""); +#endif + + if (! (device->isOpen() || device->open(QIODevice::WriteOnly))) { + status = QZipWriter::FileOpenError; + return; + } + device->seek(start_of_directory); + + // don't compress small files + QZipWriter::CompressionPolicy compression = compressionPolicy; + if (compressionPolicy == QZipWriter::AutoCompress) { + if (contents.length() < 64) + compression = QZipWriter::NeverCompress; + else + compression = QZipWriter::AlwaysCompress; + } + + FileHeader header; + memset(&header.h, 0, sizeof(CentralFileHeader)); + writeUInt(header.h.signature, 0x02014b50); + + writeUShort(header.h.version_needed, 0x14); + writeUInt(header.h.uncompressed_size, contents.length()); + writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime()); + QByteArray data = contents; + if (compression == QZipWriter::AlwaysCompress) { + writeUShort(header.h.compression_method, 8); + + ulong len = contents.length(); + // shamelessly copied form zlib + len += (len >> 12) + (len >> 14) + 11; + int res; + do { + data.resize(len); + res = deflate((uchar*)data.data(), &len, (const uchar*)contents.constData(), contents.length()); + + switch (res) { + case Z_OK: + data.resize(len); + break; + case Z_MEM_ERROR: + qWarning("QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping"); + data.resize(0); + break; + case Z_BUF_ERROR: + len *= 2; + break; + } + } while (res == Z_BUF_ERROR); + } +// TODO add a check if data.length() > contents.length(). Then try to store the original and revert the compression method to be uncompressed + writeUInt(header.h.compressed_size, data.length()); + uint crc_32 = ::crc32(0, 0, 0); + crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length()); + writeUInt(header.h.crc_32, crc_32); + + header.file_name = fileName.toLocal8Bit(); + if (header.file_name.size() > 0xffff) { + qWarning("QZip: Filename too long, chopping it to 65535 characters"); + header.file_name = header.file_name.left(0xffff); + } + writeUShort(header.h.file_name_length, header.file_name.length()); + //h.extra_field_length[2]; + + writeUShort(header.h.version_made, 3 << 8); + //uchar internal_file_attributes[2]; + //uchar external_file_attributes[4]; + quint32 mode = permissionsToMode(permissions); + switch (type) { + case File: mode |= S_IFREG; break; + case Directory: mode |= S_IFDIR; break; + case Symlink: mode |= S_IFLNK; break; + } + writeUInt(header.h.external_file_attributes, mode << 16); + writeUInt(header.h.offset_local_header, start_of_directory); + + + fileHeaders.append(header); + + LocalFileHeader h = header.h.toLocalHeader(); + device->write((const char *)&h, sizeof(LocalFileHeader)); + device->write(header.file_name); + device->write(data); + start_of_directory = device->pos(); + dirtyFileTree = true; +} + +////////////////////////////// Reader + +/*! + \class QZipReader::FileInfo + \internal + Represents one entry in the zip table of contents. +*/ + +/*! + \variable FileInfo::filePath + The full filepath inside the archive. +*/ + +/*! + \variable FileInfo::isDir + A boolean type indicating if the entry is a directory. +*/ + +/*! + \variable FileInfo::isFile + A boolean type, if it is one this entry is a file. +*/ + +/*! + \variable FileInfo::isSymLink + A boolean type, if it is one this entry is symbolic link. +*/ + +/*! + \variable FileInfo::permissions + A list of flags for the permissions of this entry. +*/ + +/*! + \variable FileInfo::crc32 + The calculated checksum as a crc32 type. +*/ + +/*! + \variable FileInfo::size + The total size of the unpacked content. +*/ + +/*! + \variable FileInfo::d + \internal + private pointer. +*/ + +/*! + \class QZipReader + \internal + \since 4.5 + + \brief the QZipReader class provides a way to inspect the contents of a zip + archive and extract individual files from it. + + QZipReader can be used to read a zip archive either from a file or from any + device. An in-memory QBuffer for instance. The reader can be used to read + which files are in the archive using fileInfoList() and entryInfoAt() but + also to extract individual files using fileData() or even to extract all + files in the archive using extractAll() +*/ + +/*! + Create a new zip archive that operates on the \a fileName. The file will be + opened with the \a mode. +*/ +QZipReader::QZipReader(const QString &archive, QIODevice::OpenMode mode) +{ + QScopedPointer f(new QFile(archive)); + f->open(mode); + QZipReader::Status status; + if (f->error() == QFile::NoError) + status = NoError; + else { + if (f->error() == QFile::ReadError) + status = FileReadError; + else if (f->error() == QFile::OpenError) + status = FileOpenError; + else if (f->error() == QFile::PermissionsError) + status = FilePermissionsError; + else + status = FileError; + } + + d = new QZipReaderPrivate(f.data(), /*ownDevice=*/true); + f.take(); + d->status = status; +} + +/*! + Create a new zip archive that operates on the archive found in \a device. + You have to open the device previous to calling the constructor and only a + device that is readable will be scanned for zip filecontent. + */ +QZipReader::QZipReader(QIODevice *device) + : d(new QZipReaderPrivate(device, /*ownDevice=*/false)) +{ + Q_ASSERT(device); +} + +/*! + Desctructor +*/ +QZipReader::~QZipReader() +{ + close(); + delete d; +} + +/*! + Returns device used for reading zip archive. +*/ +QIODevice* QZipReader::device() const +{ + return d->device; +} + +/*! + Returns true if the user can read the file; otherwise returns false. +*/ +bool QZipReader::isReadable() const +{ + return d->device->isReadable(); +} + +/*! + Returns true if the file exists; otherwise returns false. +*/ +bool QZipReader::exists() const +{ + QFile *f = qobject_cast (d->device); + if (f == 0) + return true; + return f->exists(); +} + +/*! + Returns the list of files the archive contains. +*/ +QList QZipReader::fileInfoList() const +{ + d->scanFiles(); + QList files; + for (int i = 0; i < d->fileHeaders.size(); ++i) { + QZipReader::FileInfo fi; + d->fillFileInfo(i, fi); + files.append(fi); + } + return files; + +} + +/*! + Return the number of items in the zip archive. +*/ +int QZipReader::count() const +{ + d->scanFiles(); + return d->fileHeaders.count(); +} + +/*! + Returns a FileInfo of an entry in the zipfile. + The \a index is the index into the directory listing of the zipfile. + Returns an invalid FileInfo if \a index is out of boundaries. + + \sa fileInfoList() +*/ +QZipReader::FileInfo QZipReader::entryInfoAt(int index) const +{ + d->scanFiles(); + QZipReader::FileInfo fi; + if (index >= 0 && index < d->fileHeaders.count()) + d->fillFileInfo(index, fi); + return fi; +} + +/*! + Fetch the file contents from the zip archive and return the uncompressed bytes. +*/ +QByteArray QZipReader::fileData(const QString &fileName) const +{ + d->scanFiles(); + int i; + for (i = 0; i < d->fileHeaders.size(); ++i) { + if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName) + break; + } + if (i == d->fileHeaders.size()) + return QByteArray(); + + FileHeader header = d->fileHeaders.at(i); + + int compressed_size = readUInt(header.h.compressed_size); + int uncompressed_size = readUInt(header.h.uncompressed_size); + int start = readUInt(header.h.offset_local_header); + //qDebug("uncompressing file %d: local header at %d", i, start); + + d->device->seek(start); + LocalFileHeader lh; + d->device->read((char *)&lh, sizeof(LocalFileHeader)); + uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length); + d->device->seek(d->device->pos() + skip); + + int compression_method = readUShort(lh.compression_method); + //qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size); + + //qDebug("file at %lld", d->device->pos()); + QByteArray compressed = d->device->read(compressed_size); + if (compression_method == 0) { + // no compression + compressed.truncate(uncompressed_size); + return compressed; + } else if (compression_method == 8) { + // Deflate + //qDebug("compressed=%d", compressed.size()); + compressed.truncate(compressed_size); + QByteArray baunzip; + ulong len = qMax(uncompressed_size, 1); + int res; + do { + baunzip.resize(len); + res = inflate((uchar*)baunzip.data(), &len, + (uchar*)compressed.constData(), compressed_size); + + switch (res) { + case Z_OK: + if ((int)len != baunzip.size()) + baunzip.resize(len); + break; + case Z_MEM_ERROR: + qWarning("QZip: Z_MEM_ERROR: Not enough memory"); + break; + case Z_BUF_ERROR: + len *= 2; + break; + case Z_DATA_ERROR: + qWarning("QZip: Z_DATA_ERROR: Input data is corrupted"); + break; + } + } while (res == Z_BUF_ERROR); + return baunzip; + } + qWarning() << "QZip: Unknown compression method"; + return QByteArray(); +} + +/*! + Extracts the full contents of the zip file into \a destinationDir on + the local filesystem. + In case writing or linking a file fails, the extraction will be aborted. +*/ +bool QZipReader::extractAll(const QString &destinationDir) const +{ + QDir baseDir(destinationDir); + + // create directories first + QList allFiles = fileInfoList(); + foreach (FileInfo fi, allFiles) { + const QString absPath = destinationDir + QDir::separator() + fi.filePath; + if (fi.isDir) { + if (!baseDir.mkpath(fi.filePath)) + return false; + if (!QFile::setPermissions(absPath, fi.permissions)) + return false; + } + } + + // set up symlinks + foreach (FileInfo fi, allFiles) { + const QString absPath = destinationDir + QDir::separator() + fi.filePath; + if (fi.isSymLink) { + QString destination = QFile::decodeName(fileData(fi.filePath)); + if (destination.isEmpty()) + return false; + QFileInfo linkFi(absPath); + if (!QFile::exists(linkFi.absolutePath())) + QDir::root().mkpath(linkFi.absolutePath()); + if (!QFile::link(destination, absPath)) + return false; + /* cannot change permission of links + if (!QFile::setPermissions(absPath, fi.permissions)) + return false; + */ + } + } + + foreach (FileInfo fi, allFiles) { + const QString absPath = destinationDir + QDir::separator() + fi.filePath; + if (fi.isFile) { + QFile f(absPath); + if (!f.open(QIODevice::WriteOnly)) + return false; + f.write(fileData(fi.filePath)); + f.setPermissions(fi.permissions); + f.close(); + } + } + + return true; +} + +/*! + \enum QZipReader::Status + + The following status values are possible: + + \value NoError No error occurred. + \value FileReadError An error occurred when reading from the file. + \value FileOpenError The file could not be opened. + \value FilePermissionsError The file could not be accessed. + \value FileError Another file error occurred. +*/ + +/*! + Returns a status code indicating the first error that was met by QZipReader, + or QZipReader::NoError if no error occurred. +*/ +QZipReader::Status QZipReader::status() const +{ + return d->status; +} + +/*! + Close the zip file. +*/ +void QZipReader::close() +{ + d->device->close(); +} + +////////////////////////////// Writer + +/*! + \class QZipWriter + \internal + \since 4.5 + + \brief the QZipWriter class provides a way to create a new zip archive. + + QZipWriter can be used to create a zip archive containing any number of files + and directories. The files in the archive will be compressed in a way that is + compatible with common zip reader applications. +*/ + + +/*! + Create a new zip archive that operates on the \a archive filename. The file will + be opened with the \a mode. + \sa isValid() +*/ +QZipWriter::QZipWriter(const QString &fileName, QIODevice::OpenMode mode) +{ + QScopedPointer f(new QFile(fileName)); + f->open(mode); + QZipWriter::Status status; + if (f->error() == QFile::NoError) + status = QZipWriter::NoError; + else { + if (f->error() == QFile::WriteError) + status = QZipWriter::FileWriteError; + else if (f->error() == QFile::OpenError) + status = QZipWriter::FileOpenError; + else if (f->error() == QFile::PermissionsError) + status = QZipWriter::FilePermissionsError; + else + status = QZipWriter::FileError; + } + + d = new QZipWriterPrivate(f.data(), /*ownDevice=*/true); + f.take(); + d->status = status; +} + +/*! + Create a new zip archive that operates on the archive found in \a device. + You have to open the device previous to calling the constructor and + only a device that is readable will be scanned for zip filecontent. + */ +QZipWriter::QZipWriter(QIODevice *device) + : d(new QZipWriterPrivate(device, /*ownDevice=*/false)) +{ + Q_ASSERT(device); +} + +QZipWriter::~QZipWriter() +{ + close(); + delete d; +} + +/*! + Returns device used for writing zip archive. +*/ +QIODevice* QZipWriter::device() const +{ + return d->device; +} + +/*! + Returns true if the user can write to the archive; otherwise returns false. +*/ +bool QZipWriter::isWritable() const +{ + return d->device->isWritable(); +} + +/*! + Returns true if the file exists; otherwise returns false. +*/ +bool QZipWriter::exists() const +{ + QFile *f = qobject_cast (d->device); + if (f == 0) + return true; + return f->exists(); +} + +/*! + \enum QZipWriter::Status + + The following status values are possible: + + \value NoError No error occurred. + \value FileWriteError An error occurred when writing to the device. + \value FileOpenError The file could not be opened. + \value FilePermissionsError The file could not be accessed. + \value FileError Another file error occurred. +*/ + +/*! + Returns a status code indicating the first error that was met by QZipWriter, + or QZipWriter::NoError if no error occurred. +*/ +QZipWriter::Status QZipWriter::status() const +{ + return d->status; +} + +/*! + \enum QZipWriter::CompressionPolicy + + \value AlwaysCompress A file that is added is compressed. + \value NeverCompress A file that is added will be stored without changes. + \value AutoCompress A file that is added will be compressed only if that will give a smaller file. +*/ + +/*! + Sets the policy for compressing newly added files to the new \a policy. + + \note the default policy is AlwaysCompress + + \sa compressionPolicy() + \sa addFile() +*/ +void QZipWriter::setCompressionPolicy(CompressionPolicy policy) +{ + d->compressionPolicy = policy; +} + +/*! + Returns the currently set compression policy. + \sa setCompressionPolicy() + \sa addFile() +*/ +QZipWriter::CompressionPolicy QZipWriter::compressionPolicy() const +{ + return d->compressionPolicy; +} + +/*! + Sets the permissions that will be used for newly added files. + + \note the default permissions are QFile::ReadOwner | QFile::WriteOwner. + + \sa creationPermissions() + \sa addFile() +*/ +void QZipWriter::setCreationPermissions(QFile::Permissions permissions) +{ + d->permissions = permissions; +} + +/*! + Returns the currently set creation permissions. + + \sa setCreationPermissions() + \sa addFile() +*/ +QFile::Permissions QZipWriter::creationPermissions() const +{ + return d->permissions; +} + +/*! + Add a file to the archive with \a data as the file contents. + The file will be stored in the archive using the \a fileName which + includes the full path in the archive. + + The new file will get the file permissions based on the current + creationPermissions and it will be compressed using the zip compression + based on the current compression policy. + + \sa setCreationPermissions() + \sa setCompressionPolicy() +*/ +void QZipWriter::addFile(const QString &fileName, const QByteArray &data) +{ + d->addEntry(QZipWriterPrivate::File, fileName, data); +} + +/*! + Add a file to the archive with \a device as the source of the contents. + The contents returned from QIODevice::readAll() will be used as the + filedata. + The file will be stored in the archive using the \a fileName which + includes the full path in the archive. +*/ +void QZipWriter::addFile(const QString &fileName, QIODevice *device) +{ + Q_ASSERT(device); + QIODevice::OpenMode mode = device->openMode(); + bool opened = false; + if ((mode & QIODevice::ReadOnly) == 0) { + opened = true; + if (! device->open(QIODevice::ReadOnly)) { + d->status = FileOpenError; + return; + } + } + d->addEntry(QZipWriterPrivate::File, fileName, device->readAll()); + if (opened) + device->close(); +} + +/*! + Create a new directory in the archive with the specified \a dirName and + the \a permissions; +*/ +void QZipWriter::addDirectory(const QString &dirName) +{ + QString name = dirName; + // separator is mandatory + if (!name.endsWith(QDir::separator())) + name.append(QDir::separator()); + d->addEntry(QZipWriterPrivate::Directory, name, QByteArray()); +} + +/*! + Create a new symbolic link in the archive with the specified \a dirName + and the \a permissions; + A symbolic link contains the destination (relative) path and name. +*/ +void QZipWriter::addSymLink(const QString &fileName, const QString &destination) +{ + d->addEntry(QZipWriterPrivate::Symlink, fileName, QFile::encodeName(destination)); +} + +/*! + Closes the zip file. +*/ +void QZipWriter::close() +{ + if (!(d->device->openMode() & QIODevice::WriteOnly)) { + d->device->close(); + return; + } + + //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size()); + d->device->seek(d->start_of_directory); + // write new directory + for (int i = 0; i < d->fileHeaders.size(); ++i) { + const FileHeader &header = d->fileHeaders.at(i); + d->device->write((const char *)&header.h, sizeof(CentralFileHeader)); + d->device->write(header.file_name); + d->device->write(header.extra_field); + d->device->write(header.file_comment); + } + int dir_size = d->device->pos() - d->start_of_directory; + // write end of directory + EndOfDirectory eod; + memset(&eod, 0, sizeof(EndOfDirectory)); + writeUInt(eod.signature, 0x06054b50); + //uchar this_disk[2]; + //uchar start_of_directory_disk[2]; + writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size()); + writeUShort(eod.num_dir_entries, d->fileHeaders.size()); + writeUInt(eod.directory_size, dir_size); + writeUInt(eod.dir_start_offset, d->start_of_directory); + writeUShort(eod.comment_length, d->comment.length()); + + d->device->write((const char *)&eod, sizeof(EndOfDirectory)); + d->device->write(d->comment); + d->device->close(); +} + +QT_END_NAMESPACE diff --git a/code/ryzom/tools/client/ryzom_installer/src/qzipreader.h b/code/ryzom/tools/client/ryzom_installer/src/qzipreader.h new file mode 100644 index 000000000..720846ae2 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/qzipreader.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QZIPREADER_H +#define QZIPREADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QZipReader class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QZipReaderPrivate; + +class QZipReader +{ +public: + QZipReader(const QString &fileName, QIODevice::OpenMode mode = QIODevice::ReadOnly ); + + explicit QZipReader(QIODevice *device); + ~QZipReader(); + + QIODevice* device() const; + + bool isReadable() const; + bool exists() const; + + struct FileInfo + { + FileInfo(); + FileInfo(const FileInfo &other); + ~FileInfo(); + FileInfo &operator=(const FileInfo &other); + bool isValid() const; + QString filePath; + uint isDir : 1; + uint isFile : 1; + uint isSymLink : 1; + QFile::Permissions permissions; + uint crc32; + qint64 size; + QDateTime lastModified; + void *d; + }; + + QList fileInfoList() const; + int count() const; + + FileInfo entryInfoAt(int index) const; + QByteArray fileData(const QString &fileName) const; + bool extractAll(const QString &destinationDir) const; + + enum Status { + NoError, + FileReadError, + FileOpenError, + FilePermissionsError, + FileError + }; + + Status status() const; + + void close(); + +private: + QZipReaderPrivate *d; + Q_DISABLE_COPY(QZipReader) +}; + +QT_END_NAMESPACE + +#endif // QZIPREADER_H diff --git a/code/ryzom/tools/client/ryzom_installer/src/qzipwriter.h b/code/ryzom/tools/client/ryzom_installer/src/qzipwriter.h new file mode 100644 index 000000000..a92595479 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/qzipwriter.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QZIPWRITER_H +#define QZIPWRITER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QZipWriter class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QZipWriterPrivate; + + +class QZipWriter +{ +public: + QZipWriter(const QString &fileName, QIODevice::OpenMode mode = (QIODevice::WriteOnly | QIODevice::Truncate) ); + + explicit QZipWriter(QIODevice *device); + ~QZipWriter(); + + QIODevice* device() const; + + bool isWritable() const; + bool exists() const; + + enum Status { + NoError, + FileWriteError, + FileOpenError, + FilePermissionsError, + FileError + }; + + Status status() const; + + enum CompressionPolicy { + AlwaysCompress, + NeverCompress, + AutoCompress + }; + + void setCompressionPolicy(CompressionPolicy policy); + CompressionPolicy compressionPolicy() const; + + void setCreationPermissions(QFile::Permissions permissions); + QFile::Permissions creationPermissions() const; + + void addFile(const QString &fileName, const QByteArray &data); + + void addFile(const QString &fileName, QIODevice *device); + + void addDirectory(const QString &dirName); + + void addSymLink(const QString &fileName, const QString &destination); + + void close(); +private: + QZipWriterPrivate *d; + Q_DISABLE_COPY(QZipWriter) +}; + +QT_END_NAMESPACE + +#endif // QZIPWRITER_H diff --git a/code/ryzom/tools/client/ryzom_installer/src/serversmodel.cpp b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.cpp new file mode 100644 index 000000000..7cda521de --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.cpp @@ -0,0 +1,82 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "serversmodel.h" + +CServersModel::CServersModel(QObject *parent):QAbstractListModel(parent) +{ + m_servers = CConfigFile::getInstance()->getServers(); +} + +CServersModel::CServersModel(const CServers &servers, QObject *parent):QAbstractListModel(parent), m_servers(servers) +{ +} + +CServersModel::~CServersModel() +{ +} + +int CServersModel::rowCount(const QModelIndex &parent) const +{ + return m_servers.size(); +} + +QVariant CServersModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole) return QVariant(); + + const CServer &server = m_servers.at(index.row()); + + return server.name; +} + +bool CServersModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row < 0) return false; + + beginRemoveRows(parent, row, row + count - 1); + + m_servers.removeAt(row); + + endRemoveRows(); + + return true; +} + +bool CServersModel::save() const +{ + CConfigFile::getInstance()->setServers(m_servers); + + return true; +} + +int CServersModel::getIndexFromServerID(const QString &serverId) const +{ + for(int i = 0; i < m_servers.size(); ++i) + { + if (m_servers[i].id == serverId) return i; + } + + return -1; +} + +QString CServersModel::getServerIDFromIndex(int index) const +{ + if (index < 0 || index >= m_servers.size()) return ""; + + return m_servers[index].id; +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/serversmodel.h b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.h new file mode 100644 index 000000000..27f5daed3 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.h @@ -0,0 +1,51 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef SERVERSMODEL_H +#define SERVERSMODEL_H + +#include "configfile.h" + +/** + * Servers model + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CServersModel : public QAbstractListModel +{ +public: + CServersModel(QObject *parent); + CServersModel(const CServers &servers, QObject *parent); + virtual ~CServersModel(); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex()); + + CServers& getServers() { return m_servers; } + + bool save() const; + + int getIndexFromServerID(const QString &serverId) const; + QString getServerIDFromIndex(int index) const; + +private: + CServers m_servers; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.cpp new file mode 100644 index 000000000..a919bc2c7 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.cpp @@ -0,0 +1,38 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "settingsdialog.h" + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +CSettingsDialog::CSettingsDialog():QDialog() +{ + setupUi(this); +} + +CSettingsDialog::~CSettingsDialog() +{ +} + +void CSettingsDialog::accept() +{ + // TODO: add save code + + QDialog::accept(); +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.h b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.h new file mode 100644 index 000000000..f8e3144f7 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.h @@ -0,0 +1,40 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include "ui_settings.h" + +/** + * Settings dialog + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CSettingsDialog : public QDialog, public Ui::SettingsDialog +{ + Q_OBJECT + +public: + CSettingsDialog(); + virtual ~CSettingsDialog(); + +private slots: + void accept(); +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/stdpch.cpp b/code/ryzom/tools/client/ryzom_installer/src/stdpch.cpp new file mode 100644 index 000000000..a3d45576c --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/stdpch.cpp @@ -0,0 +1,17 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" diff --git a/code/ryzom/tools/client/ryzom_installer/src/stdpch.h b/code/ryzom/tools/client/ryzom_installer/src/stdpch.h new file mode 100644 index 000000000..5d64b580f --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/stdpch.h @@ -0,0 +1,65 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef STDPCH_H +#define STDPCH_H + +#if defined(_MSC_VER) && defined(_DEBUG) + #define _CRTDBG_MAP_ALLOC + #include + #include + #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) + #undef realloc +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifndef _DEBUG +#define QT_NO_DEBUG_OUTPUT +#endif + +#include + +#ifdef Q_COMPILER_RVALUE_REFS +#undef Q_COMPILER_RVALUE_REFS +#endif + +#include +#include +#include + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) +#define USE_QT5 +#endif + +#ifdef USE_QT5 +#include +#include +#endif + +#include + +#include +#include + +#endif + diff --git a/code/ryzom/tools/client/ryzom_installer/src/utils.cpp b/code/ryzom/tools/client/ryzom_installer/src/utils.cpp new file mode 100644 index 000000000..aa060a1de --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/utils.cpp @@ -0,0 +1,182 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "utils.h" + +QString qFromUtf8(const std::string &str) +{ + return QString::fromUtf8(str.c_str()); +} + +std::string qToUtf8(const QString &str) +{ + return str.toUtf8().constData(); +} + +QString qFromUtf16(const ucstring &str) +{ + return QString::fromUtf16(str.c_str()); +} + +ucstring qToUtf16(const QString &str) +{ + return ucstring::makeFromUtf8(qToUtf8(str)); +} + +QString qFromWide(const wchar_t *str) +{ + return QString::fromUtf16((ushort*)str); +} + +wchar_t* qToWide(const QString &str) +{ + return (wchar_t*)str.utf16(); +} + + +// CreateLink - Uses the Shell's IShellLink and IPersistFile interfaces +// to create and store a shortcut to the specified object. +// +// Returns the result of calling the member functions of the interfaces. +// +// Parameters: +// lpszPathObj - Address of a buffer that contains the path of the object, +// including the file name. +// lpszPathLink - Address of a buffer that contains the path where the +// Shell link is to be stored, including the file name. +// lpszDesc - Address of a buffer that contains a description of the +// Shell link, stored in the Comment field of the link +// properties. + +HRESULT CreateLink(const QString &pathObj, const QString &pathLink, const QString &desc) +{ + IShellLinkW* psl; + + // Get a pointer to the IShellLink interface. It is assumed that CoInitialize + // has already been called. + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); + if (SUCCEEDED(hres)) + { + IPersistFile* ppf; + + // Set the path to the shortcut target and add the description. + psl->SetPath(qToWide(pathObj)); + psl->SetDescription(qToWide(desc)); + psl->SetArguments(L"--profil "); + psl->SetWorkingDirectory(L""); + + // Query IShellLink for the IPersistFile interface, used for saving the + // shortcut in persistent storage. + hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); + + if (SUCCEEDED(hres)) + { + // Add code here to check return value from MultiByteWideChar + // for success. + + // Save the link by calling IPersistFile::Save. + hres = ppf->Save(qToWide(pathLink), TRUE); + ppf->Release(); + } + psl->Release(); + } + return hres; +} + +// ResolveIt - Uses the Shell's IShellLink and IPersistFile interfaces +// to retrieve the path and description from an existing shortcut. +// +// Returns the result of calling the member functions of the interfaces. +// +// Parameters: +// hwnd - A handle to the parent window. The Shell uses this window to +// display a dialog box if it needs to prompt the user for more +// information while resolving the link. +// lpszLinkFile - Address of a buffer that contains the path of the link, +// including the file name. +// lpszPath - Address of a buffer that receives the path of the link +// target, including the file name. +// lpszDesc - Address of a buffer that receives the description of the +// Shell link, stored in the Comment field of the link +// properties. + +HRESULT ResolveIt(HWND hwnd, const QString &linkFile, QString &path) +{ + IShellLinkW* psl; + WIN32_FIND_DATAW wfd; + + path.clear(); // Assume failure + + // Get a pointer to the IShellLink interface. It is assumed that CoInitialize + // has already been called. + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); + if (SUCCEEDED(hres)) + { + IPersistFile* ppf; + + // Get a pointer to the IPersistFile interface. + hres = psl->QueryInterface(IID_IPersistFile, (void**)&ppf); + + if (SUCCEEDED(hres)) + { + // Add code here to check return value from MultiByteWideChar + // for success. + + // Load the shortcut. + hres = ppf->Load(qToWide(linkFile), STGM_READ); + + if (SUCCEEDED(hres)) + { + // Resolve the link. + hres = psl->Resolve(hwnd, 0); + + if (SUCCEEDED(hres)) + { + WCHAR szGotPath[MAX_PATH]; + + // Get the path to the link target. + hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATAW*)&wfd, SLGP_SHORTPATH); + + if (SUCCEEDED(hres)) + { + WCHAR szDescription[MAX_PATH]; + + // Get the description of the target. + hres = psl->GetDescription(szDescription, MAX_PATH); + + if (SUCCEEDED(hres)) + { + // Handle success + path = qFromWide(szGotPath); + } + else + { + } + } + } + } + + // Release the pointer to the IPersistFile interface. + ppf->Release(); + } + + // Release the pointer to the IShellLink interface. + psl->Release(); + } + + return hres; +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/utils.h b/code/ryzom/tools/client/ryzom_installer/src/utils.h new file mode 100644 index 000000000..179adae4d --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/utils.h @@ -0,0 +1,47 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef UTILS_H +#define UTILS_H + +#include + +#include + +/** + * Utils functions + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ + +// Convert a UTF-8 string to QString +QString qFromUtf8(const std::string &str); + +// Convert a QString to UTF-8 string +std::string qToUtf8(const QString &str); + +// Convert a UTF-16 string to QString +QString qFromUtf16(const ucstring &str); + +// Convert a QString to UTF-16 string +ucstring qToUtf16(const QString &str); + +QString qFromWide(const wchar_t *str); + +wchar_t* qToWide(const QString &str); + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.cpp new file mode 100644 index 000000000..1cb13c58a --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.cpp @@ -0,0 +1,219 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "wizarddialog.h" +#include "configfile.h" + +#include "nel/misc/system_info.h" +#include "nel/misc/common.h" + +#ifdef DEBUG_NEW + #define new DEBUG_NEW +#endif + +QString qBytesToHumanReadable(qint64 bytes) +{ + static std::vector units; + + if (units.empty()) + { + units.push_back(QObject::tr("B").toUtf8().constData()); + units.push_back(QObject::tr("KiB").toUtf8().constData()); + units.push_back(QObject::tr("MiB").toUtf8().constData()); + units.push_back(QObject::tr("GiB").toUtf8().constData()); + units.push_back(QObject::tr("TiB").toUtf8().constData()); + units.push_back(QObject::tr("PiB").toUtf8().constData()); + } + + return QString::fromUtf8(NLMISC::bytesToHumanReadable(bytes).c_str()); +} + +CWizardDialog::CWizardDialog():QDialog() +{ + setupUi(this); + + currentDirectoryRadioButton->setVisible(false); + oldDirectoryRadioButton->setVisible(false); + + // enable download radio button by default + internetRadioButton->setChecked(true); + + // if launched from current directory, it means we just patched files + m_currentDirectory = CConfigFile::getInstance()->getCurrentDirectory(); + + if (!CConfigFile::getInstance()->isRyzomInstalledIn(m_currentDirectory)) + { + // current directory is a child of Ryzom root directory + m_currentDirectory = CConfigFile::getInstance()->getParentDirectory(); + + if (!CConfigFile::getInstance()->isRyzomInstalledIn(m_currentDirectory)) + { + // Ryzom is in the same directory as Ryzom Installer + m_currentDirectory = CConfigFile::getInstance()->getApplicationDirectory(); + + if (!CConfigFile::getInstance()->isRyzomInstalledIn(m_currentDirectory)) + { + m_currentDirectory.clear(); + } + } + } + + // display found directory + if (!m_currentDirectory.isEmpty()) + { + currentDirectoryRadioButton->setText(tr("Current directory: %1").arg(m_currentDirectory)); + currentDirectoryRadioButton->setVisible(true); + currentDirectoryRadioButton->setChecked(true); + } + + m_oldDirectory = CConfigFile::getInstance()->getOldInstallationDirectory(); + + // found a previous installation + if (CConfigFile::getInstance()->areRyzomDataInstalledIn(m_oldDirectory)) + { + oldDirectoryRadioButton->setText(tr("Old installation: %1").arg(m_oldDirectory)); + oldDirectoryRadioButton->setVisible(true); + + if (m_currentDirectory.isEmpty()) oldDirectoryRadioButton->setChecked(true); + } + + updateAnotherLocationText(); + + m_dstDirectory = CConfigFile::getNewInstallationDirectory(); + + updateDestinationText(); + + // check whether OS architecture is 32 or 64 bits + // TODO: 64 bits client only supported under Vista+ + if (CConfigFile::has64bitsOS()) + { + clientArchGroupBox->setVisible(true); + clientArch64RadioButton->setChecked(true); + } + else + { + clientArchGroupBox->setVisible(false); + clientArch32RadioButton->setChecked(true); + } + + const CServer &server = CConfigFile::getInstance()->getServer(); + + internetRadioButton->setText(tr("Internet (%1 to download)").arg(qBytesToHumanReadable(server.dataCompressedSize))); + destinationGroupBox->setTitle(tr("Files will be installed to (requires %1):").arg(qBytesToHumanReadable(server.dataUncompressedSize))); + + connect(anotherLocationBrowseButton, SIGNAL(clicked()), SLOT(onAnotherLocationBrowseButtonClicked())); + connect(destinationBrowseButton, SIGNAL(clicked()), SLOT(onDestinationBrowseButtonClicked())); + + // TODO: if found a folder with initial data, get its total size and check if at least that size is free on the disk + + // by default, advanced parameters are hidden + onShowAdvancedParameters(Qt::Unchecked); + + connect(advancedCheckBox, SIGNAL(stateChanged(int)), SLOT(onShowAdvancedParameters(int))); +} + +CWizardDialog::~CWizardDialog() +{ +} + +void CWizardDialog::onShowAdvancedParameters(int state) +{ + advancedFrame->setVisible(state != Qt::Unchecked); + + adjustSize(); +} + +void CWizardDialog::onAnotherLocationBrowseButtonClicked() +{ + QString directory; + + for(;;) + { + directory = QFileDialog::getExistingDirectory(this, tr("Please choose directory where is installed Ryzom")); + + if (directory.isEmpty()) return; + + if (CConfigFile::getInstance()->isRyzomInstalledIn(directory)) break; + + QMessageBox::StandardButton res = QMessageBox::warning(this, tr("Unable to find Ryzom"), tr("Unable to find Ryzom in selected directory. Please choose another one or cancel.")); + } + + m_anotherDirectory = directory; + + // if we browse to a Ryzom installation, we want to use it + anotherLocationRadioButton->setChecked(true); + + updateAnotherLocationText(); +} + +void CWizardDialog::onDestinationBrowseButtonClicked() +{ + QString directory = QFileDialog::getExistingDirectory(this, tr("Please choose directory where to install Ryzom")); + + if (directory.isEmpty()) return; + + m_dstDirectory = directory; + + updateDestinationText(); +} + +void CWizardDialog::updateAnotherLocationText() +{ + anotherLocationRadioButton->setText(tr("Another location: %1").arg(m_anotherDirectory.isEmpty() ? tr("Undefined"):m_anotherDirectory)); +} + +void CWizardDialog::updateDestinationText() +{ + destinationLabel->setText(m_dstDirectory); +} + +void CWizardDialog::accept() +{ + // check free disk space + qint64 freeSpace = NLMISC::CSystemInfo::availableHDSpace(m_dstDirectory.toUtf8().constData()); + + const CServer &server = CConfigFile::getInstance()->getServer(); + + if (freeSpace < server.dataUncompressedSize) + { + QMessageBox::StandardButton res = QMessageBox::warning(this, tr("Not enough free disk space"), tr("You don't have enough free space on this disk, please make more space or choose a directory on another disk.")); + return; + } + + if (currentDirectoryRadioButton->isChecked()) + { + CConfigFile::getInstance()->setSrcServerDirectory(m_currentDirectory); + } + else if (oldDirectoryRadioButton->isChecked()) + { + CConfigFile::getInstance()->setSrcServerDirectory(m_oldDirectory); + } + else if (anotherLocationRadioButton->isChecked()) + { + CConfigFile::getInstance()->setSrcServerDirectory(m_anotherDirectory); + } + else + { + CConfigFile::getInstance()->setSrcServerDirectory(""); + } + + CConfigFile::getInstance()->setInstallationDirectory(m_dstDirectory); + CConfigFile::getInstance()->setUse64BitsClient(clientArch64RadioButton->isChecked()); + CConfigFile::getInstance()->save(); + + QDialog::accept(); +} diff --git a/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.h b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.h new file mode 100644 index 000000000..8ac146184 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.h @@ -0,0 +1,53 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef WIZARDDIALOG_H +#define WIZARDDIALOG_H + +#include "ui_wizard.h" + +/** + * Wizard displayed at first launch, that asks user to choose source and destination directories. + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CWizardDialog : public QDialog, public Ui::WizardDialog +{ + Q_OBJECT + +public: + CWizardDialog(); + virtual ~CWizardDialog(); + +private slots: + void onShowAdvancedParameters(int state); + void onAnotherLocationBrowseButtonClicked(); + void onDestinationBrowseButtonClicked(); + + void accept(); + +private: + void updateAnotherLocationText(); + void updateDestinationText(); + + QString m_currentDirectory; + QString m_oldDirectory; + QString m_anotherDirectory; + QString m_dstDirectory; +}; + +#endif diff --git a/code/ryzom/tools/client/ryzom_installer/ui/mainwindow.ui b/code/ryzom/tools/client/ryzom_installer/ui/mainwindow.ui new file mode 100644 index 000000000..ee9bd7b0e --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/ui/mainwindow.ui @@ -0,0 +1,212 @@ + + + MainWindow + + + + 0 + 0 + 627 + 539 + + + + Ryzom Installer + + + + + + + + + + + + :/images/background.png + + + + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + Qt::AlignCenter + + + %p% + + + + + + + Resume + + + + + + + Stop + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Atys + + + + + + + + Play + + + + + + + Configure + + + + + + + + + + + + 0 + 0 + 627 + 21 + + + + + &Settings + + + + + + + + + &Help + + + + + + + + + + false + + + + + About Qt + + + + + About... + + + + + &Profiles + + + + + &Directories + + + + + &Quit + + + + + + + + diff --git a/code/ryzom/tools/client/ryzom_installer/ui/profiles.ui b/code/ryzom/tools/client/ryzom_installer/ui/profiles.ui new file mode 100644 index 000000000..1e3e42768 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/ui/profiles.ui @@ -0,0 +1,305 @@ + + + ProfilesDialog + + + + 0 + 0 + 583 + 368 + + + + Dialog + + + + + + + + + + List of profiles: + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + QAbstractItemView::NoEditTriggers + + + + + + + + + Add + + + + + + + Delete + + + + + + + + + + + + + Profile: + + + + + + + 0 + + + + + + + Account: + + + + + + + + + + Name: + + + + + + + + + + Server: + + + + + + + + Atys + + + + + Yubo + + + + + + + + Executable: + + + + + + + + + ryzom_client_r.exe + + + + + + + Browse... + + + + + + + + + Client version: + + + + + + + FV 3.0.0 + + + + + + + Arguments: + + + + + + + + + + Comments: + + + + + + + + 0 + 1 + + + + + + + + Directory: + + + + + + + + + ~/.ryzom/0 + + + + + + + Open + + + + + + + + + Create shortcuts: + + + + + + + + + Desktop + + + + + + + Start Menu + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + profilesListView + addButton + deleteButton + accountEdit + nameEdit + serverComboBox + executableBrowseButton + argumentsEdit + commentsEdit + directoryButton + desktopShortcutCheckBox + menuShortcutCheckBox + + + + + buttonBox + accepted() + ProfilesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ProfilesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/code/ryzom/tools/client/ryzom_installer/ui/settings.ui b/code/ryzom/tools/client/ryzom_installer/ui/settings.ui new file mode 100644 index 000000000..1ab8f32af --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/ui/settings.ui @@ -0,0 +1,113 @@ + + + SettingsDialog + + + + 0 + 0 + 400 + 150 + + + + Dialog + + + + + + + + Language + + + + + + + + + + Base location of Ryzom files: %1 + + + + + + + Browse... + + + + + + + Location of source Ryzom files: %1 + + + + + + + Browse... + + + + + + + Use 64 bits client + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/code/ryzom/tools/client/ryzom_installer/ui/wizard.ui b/code/ryzom/tools/client/ryzom_installer/ui/wizard.ui new file mode 100644 index 000000000..b7b6475e0 --- /dev/null +++ b/code/ryzom/tools/client/ryzom_installer/ui/wizard.ui @@ -0,0 +1,260 @@ + + + WizardDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 402 + 464 + + + + Ryzom Installer + + + true + + + + QLayout::SetFixedSize + + + + + Welcome to Ryzom Installer! + +This program will allow you to download, install, migrate, configure or manage Ryzom on your computer. + +Just follow the different steps and make your choice between the different propositions. + + + Qt::AlignJustify|Qt::AlignTop + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Show advanced parameters (expert) + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + 0 + + + 9 + + + 0 + + + 0 + + + + + Files will be installed from: + + + + + + Current directory + + + true + + + + + + + Old installation: %1 + + + true + + + + + + + 6 + + + + + Another location: %1 + + + true + + + + + + + Browse... + + + + + + + + + Internet (%1 GiB to download) + + + + + + + + + + Files will be installed to (requires 10 GiB): + + + + + + c:\ + + + + + + + Browse... + + + + + + + + + + Do you prefer to use a 64 or 32 bits client? + + + + + + 64 bits (recommended) + + + true + + + + + + + 32 bits + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + currentDirectoryRadioButton + oldDirectoryRadioButton + anotherLocationRadioButton + anotherLocationBrowseButton + internetRadioButton + destinationBrowseButton + clientArch64RadioButton + clientArch32RadioButton + + + + + buttonBox + accepted() + WizardDialog + accept() + + + 227 + 406 + + + 157 + 274 + + + + + buttonBox + rejected() + WizardDialog + reject() + + + 295 + 412 + + + 286 + 274 + + + + +