From 8f0478551be25f5917cb88d5c2c16401b1077ab5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Jun 2013 16:44:11 -0700 Subject: [PATCH 01/10] add the quazip library and a find module --- cmake/modules/FindQuazip.cmake | 42 ++ .../external/quazip/include/JlCompress.h | 114 +++++ interface/external/quazip/include/crypt.h | 135 ++++++ interface/external/quazip/include/ioapi.h | 77 +++ .../external/quazip/include/quaadler32.h | 29 ++ .../external/quazip/include/quachecksum32.h | 54 +++ interface/external/quazip/include/quacrc32.h | 26 ++ .../external/quazip/include/quagzipfile.h | 84 ++++ .../external/quazip/include/quaziodevice.h | 74 +++ interface/external/quazip/include/quazip.h | 419 +++++++++++++++++ .../external/quazip/include/quazip_global.h | 55 +++ interface/external/quazip/include/quazipdir.h | 171 +++++++ .../external/quazip/include/quazipfile.h | 442 ++++++++++++++++++ .../external/quazip/include/quazipfileinfo.h | 73 +++ .../external/quazip/include/quazipnewinfo.h | 121 +++++ interface/external/quazip/include/unzip.h | 356 ++++++++++++++ interface/external/quazip/include/zip.h | 245 ++++++++++ .../external/quazip/lib/MacOS/libquazip.a | Bin 0 -> 230752 bytes 18 files changed, 2517 insertions(+) create mode 100644 cmake/modules/FindQuazip.cmake create mode 100644 interface/external/quazip/include/JlCompress.h create mode 100644 interface/external/quazip/include/crypt.h create mode 100644 interface/external/quazip/include/ioapi.h create mode 100644 interface/external/quazip/include/quaadler32.h create mode 100644 interface/external/quazip/include/quachecksum32.h create mode 100644 interface/external/quazip/include/quacrc32.h create mode 100644 interface/external/quazip/include/quagzipfile.h create mode 100644 interface/external/quazip/include/quaziodevice.h create mode 100644 interface/external/quazip/include/quazip.h create mode 100644 interface/external/quazip/include/quazip_global.h create mode 100644 interface/external/quazip/include/quazipdir.h create mode 100644 interface/external/quazip/include/quazipfile.h create mode 100644 interface/external/quazip/include/quazipfileinfo.h create mode 100644 interface/external/quazip/include/quazipnewinfo.h create mode 100644 interface/external/quazip/include/unzip.h create mode 100644 interface/external/quazip/include/zip.h create mode 100644 interface/external/quazip/lib/MacOS/libquazip.a diff --git a/cmake/modules/FindQuazip.cmake b/cmake/modules/FindQuazip.cmake new file mode 100644 index 0000000000..c079ee6bbb --- /dev/null +++ b/cmake/modules/FindQuazip.cmake @@ -0,0 +1,42 @@ +# Find the static Quazip library +# +# You must provide a QUAZIP_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# QUAZIP_FOUND - system found quazip +# QUAZIP_INCLUDE_DIRS - the quazip include directory +# QUAZIP_LIBRARIES - Link this to use quazip +# +# Created on 6/25/2013 by Stephen Birarda +# Copyright (c) 2013 High Fidelity +# + +if (QUAZIP_LIBRARIES AND QUAZIP_INCLUDE_DIRS) + # in cache already + set(QUAZIP_FOUND TRUE) +else (QUAZIP_LIBRARIES AND QUAZIP_INCLUDE_DIRS) + find_path(QUAZIP_INCLUDE_DIRS quazip.h ${QUAZIP_ROOT_DIR}/include) + + if (APPLE) + find_library(QUAZIP_LIBRARIES libquazip.a ${QUAZIP_ROOT_DIR}/lib/MacOS/) + endif () + + if (QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) + set(QUAZIP_FOUND TRUE) + endif (QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) + + if (QUAZIP_FOUND) + if (NOT QUAZIP_FIND_QUIETLY) + message(STATUS "Found quazip: ${QUAZIP_LIBRARIES}") + endif (NOT QUAZIP_FIND_QUIETLY) + else (QUAZIP_FOUND) + if (QUAZIP_FIND_REQUIRED) + message(FATAL_ERROR "Could not find quazip") + endif (QUAZIP_FIND_REQUIRED) + endif (QUAZIP_FOUND) + + # show the QUAZIP_INCLUDE_DIRS and QUAZIP_LIBRARIES variables only in the advanced view + mark_as_advanced(QUAZIP_INCLUDE_DIRS QUAZIP_LIBRARIES) + +endif (QUAZIP_LIBRARIES AND QUAZIP_INCLUDE_DIRS) \ No newline at end of file diff --git a/interface/external/quazip/include/JlCompress.h b/interface/external/quazip/include/JlCompress.h new file mode 100644 index 0000000000..968f7a89ad --- /dev/null +++ b/interface/external/quazip/include/JlCompress.h @@ -0,0 +1,114 @@ +#ifndef JLCOMPRESSFOLDER_H_ +#define JLCOMPRESSFOLDER_H_ + +#include "quazip.h" +#include "quazipfile.h" +#include "quazipfileinfo.h" +#include +#include +#include +#include + +/// Utility class for typical operations. +/** + This class contains a number of useful static functions to perform + simple operations, such as mass ZIP packing or extraction. + */ +class QUAZIP_EXPORT JlCompress { +private: + /// Compress a single file. + /** + \param zip Opened zip to compress the file to. + \param fileName The full path to the source file. + \param fileDest The full name of the file inside the archive. + \return true if success, false otherwise. + */ + static bool compressFile(QuaZip* zip, QString fileName, QString fileDest); + /// Compress a subdirectory. + /** + \param parentZip Opened zip containing the parent directory. + \param dir The full path to the directory to pack. + \param parentDir The full path to the directory corresponding to + the root of the ZIP. + \param recursive Whether to pack sub-directories as well or only + files. + \return true if success, false otherwise. + */ + static bool compressSubDir(QuaZip* parentZip, QString dir, QString parentDir, bool recursive = true); + /// Extract a single file. + /** + \param zip The opened zip archive to extract from. + \param fileName The full name of the file to extract. + \param fileDest The full path to the destination file. + \return true if success, false otherwise. + */ + static bool extractFile(QuaZip* zip, QString fileName, QString fileDest); + /// Remove some files. + /** + \param listFile The list of files to remove. + \return true if success, false otherwise. + */ + static bool removeFile(QStringList listFile); + +public: + /// Compress a single file. + /** + \param fileCompressed The name of the archive. + \param file The file to compress. + \return true if success, false otherwise. + */ + static bool compressFile(QString fileCompressed, QString file); + /// Compress a list of files. + /** + \param fileCompressed The name of the archive. + \param files The file list to compress. + \return true if success, false otherwise. + */ + static bool compressFiles(QString fileCompressed, QStringList files); + /// Compress a whole directory. + /** + \param fileCompressed The name of the archive. + \param dir The directory to compress. + \param recursive Whether to pack the subdirectories as well, or + just regular files. + \return true if success, false otherwise. + */ + static bool compressDir(QString fileCompressed, QString dir = QString(), bool recursive = true); + +public: + /// Extract a single file. + /** + \param fileCompressed The name of the archive. + \param fileName The file to extract. + \param fileDest The destination file, assumed to be identical to + \a file if left empty. + \return The list of the full paths of the files extracted, empty on failure. + */ + static QString extractFile(QString fileCompressed, QString fileName, QString fileDest = QString()); + /// Extract a list of files. + /** + \param fileCompressed The name of the archive. + \param files The file list to extract. + \param dir The directory to put the files to, the current + directory if left empty. + \return The list of the full paths of the files extracted, empty on failure. + */ + static QStringList extractFiles(QString fileCompressed, QStringList files, QString dir = QString()); + /// Extract a whole archive. + /** + \param fileCompressed The name of the archive. + \param dir The directory to extract to, the current directory if + left empty. + \return The list of the full paths of the files extracted, empty on failure. + */ + static QStringList extractDir(QString fileCompressed, QString dir = QString()); + /// Get the file list. + /** + \return The list of the files in the archive, or, more precisely, the + list of the entries, including both files and directories if they + are present separately. + */ + static QStringList getFileList(QString fileCompressed); +}; + +#endif /* JLCOMPRESSFOLDER_H_ */ diff --git a/interface/external/quazip/include/crypt.h b/interface/external/quazip/include/crypt.h new file mode 100644 index 0000000000..1d6da628f3 --- /dev/null +++ b/interface/external/quazip/include/crypt.h @@ -0,0 +1,135 @@ +/* crypt.h -- base code for crypt/uncrypt ZIPfile + + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This code is a modified version of crypting code in Infozip distribution + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + If you don't need crypting in your application, just define symbols + NOCRYPT and NOUNCRYPT. + + This code support the "Traditional PKWARE Encryption". + + The new AES encryption added on Zip format by Winzip (see the page + http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong + Encryption is not supported. +*/ + +#include "quazip_global.h" + +#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) + +/*********************************************************************** + * Return the next byte in the pseudo-random sequence + */ +static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab UNUSED) +{ + //(void) pcrc_32_tab; /* avoid "unused parameter" warning */ + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an + * unpredictable manner on 16-bit systems; not a problem + * with any known compiler so far, though */ + + temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*********************************************************************** + * Update the encryption keys with the next byte of plain text + */ +static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c) +{ + (*(pkeys+0)) = CRC32((*(pkeys+0)), c); + (*(pkeys+1)) += (*(pkeys+0)) & 0xff; + (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; + { + register int keyshift = (int)((*(pkeys+1)) >> 24); + (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); + } + return c; +} + + +/*********************************************************************** + * Initialize the encryption keys and the random header according to + * the given password. + */ +static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab) +{ + *(pkeys+0) = 305419896L; + *(pkeys+1) = 591751049L; + *(pkeys+2) = 878082192L; + while (*passwd != '\0') { + update_keys(pkeys,pcrc_32_tab,(int)*passwd); + passwd++; + } +} + +#define zdecode(pkeys,pcrc_32_tab,c) \ + (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) + +#define zencode(pkeys,pcrc_32_tab,c,t) \ + (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) + +#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED + +#define RAND_HEAD_LEN 12 + /* "last resort" source for second part of crypt seed pattern */ +# ifndef ZCR_SEED2 +# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ +# endif + +static int crypthead(passwd, buf, bufSize, pkeys, pcrc_32_tab, crcForCrypting) + const char *passwd; /* password string */ + unsigned char *buf; /* where to write header */ + int bufSize; + unsigned long* pkeys; + const unsigned long* pcrc_32_tab; + unsigned long crcForCrypting; +{ + int n; /* index in random header */ + int t; /* temporary */ + int c; /* random byte */ + unsigned char header[RAND_HEAD_LEN-2]; /* random header */ + static unsigned calls = 0; /* ensure different random header each time */ + + if (bufSize> 7) & 0xff; + header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); + } + /* Encrypt random header (last two bytes is high word of crc) */ + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); + } + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); + return n; +} + +#endif diff --git a/interface/external/quazip/include/ioapi.h b/interface/external/quazip/include/ioapi.h new file mode 100644 index 0000000000..f4c2180932 --- /dev/null +++ b/interface/external/quazip/include/ioapi.h @@ -0,0 +1,77 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + Modified by Sergey A. Tachenov to integrate with Qt. +*/ + +#ifndef _ZLIBIOAPI_H +#define _ZLIBIOAPI_H + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + +#if (defined(WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) +#define ZCALLBACK CALLBACK +#else +#define ZCALLBACK +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, voidpf file, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef uLong (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + + + +void fill_qiodevice_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +#define ZREAD(filefunc,filestream,buf,size) ((*((filefunc).zread_file))((filefunc).opaque,filestream,buf,size)) +#define ZWRITE(filefunc,filestream,buf,size) ((*((filefunc).zwrite_file))((filefunc).opaque,filestream,buf,size)) +#define ZTELL(filefunc,filestream) ((*((filefunc).ztell_file))((filefunc).opaque,filestream)) +#define ZSEEK(filefunc,filestream,pos,mode) ((*((filefunc).zseek_file))((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE(filefunc,filestream) ((*((filefunc).zclose_file))((filefunc).opaque,filestream)) +#define ZERROR(filefunc,filestream) ((*((filefunc).zerror_file))((filefunc).opaque,filestream)) + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/interface/external/quazip/include/quaadler32.h b/interface/external/quazip/include/quaadler32.h new file mode 100644 index 0000000000..643e38361e --- /dev/null +++ b/interface/external/quazip/include/quaadler32.h @@ -0,0 +1,29 @@ +#ifndef QUAADLER32_H +#define QUAADLER32_H + +#include + +#include "quachecksum32.h" + +/// Adler32 checksum +/** \class QuaAdler32 quaadler32.h + * This class wrappers the adler32 function with the QuaChecksum32 interface. + * See QuaChecksum32 for more info. + */ +class QUAZIP_EXPORT QuaAdler32 : public QuaChecksum32 +{ + +public: + QuaAdler32(); + + quint32 calculate(const QByteArray &data); + + void reset(); + void update(const QByteArray &buf); + quint32 value(); + +private: + quint32 checksum; +}; + +#endif //QUAADLER32_H diff --git a/interface/external/quazip/include/quachecksum32.h b/interface/external/quazip/include/quachecksum32.h new file mode 100644 index 0000000000..6837d26b86 --- /dev/null +++ b/interface/external/quazip/include/quachecksum32.h @@ -0,0 +1,54 @@ +#ifndef QUACHECKSUM32_H +#define QUACHECKSUM32_H + +#include +#include "quazip_global.h" + +/// Checksum interface. +/** \class QuaChecksum32 quachecksum32.h + * This is an interface for 32 bit checksums. + * Classes implementing this interface can calcunate a certin + * checksum in a single step: + * \code + * QChecksum32 *crc32 = new QuaCrc32(); + * rasoult = crc32->calculate(data); + * \endcode + * or by streaming the data: + * \code + * QChecksum32 *crc32 = new QuaCrc32(); + * while(!fileA.atEnd()) + * crc32->update(fileA.read(bufSize)); + * resoultA = crc32->value(); + * crc32->reset(); + * while(!fileB.atEnd()) + * crc32->update(fileB.read(bufSize)); + * resoultB = crc32->value(); + * \endcode + */ +class QUAZIP_EXPORT QuaChecksum32 +{ + +public: + ///Calculates the checksum for data. + /** \a data source data + * \return data checksum + * + * This function has no efect on the value returned by value(). + */ + virtual quint32 calculate(const QByteArray &data) = 0; + + ///Resets the calculation on a checksun for a stream. + virtual void reset() = 0; + + ///Updates the calculated checksum for the stream + /** \a buf next portion of data from the stream + */ + virtual void update(const QByteArray &buf) = 0; + + ///Value of the checksum calculated for the stream passed throw update(). + /** \return checksum + */ + virtual quint32 value() = 0; +}; + +#endif //QUACHECKSUM32_H diff --git a/interface/external/quazip/include/quacrc32.h b/interface/external/quazip/include/quacrc32.h new file mode 100644 index 0000000000..4c86d56657 --- /dev/null +++ b/interface/external/quazip/include/quacrc32.h @@ -0,0 +1,26 @@ +#ifndef QUACRC32_H +#define QUACRC32_H + +#include "quachecksum32.h" + +///CRC32 checksum +/** \class QuaCrc32 quacrc32.h +* This class wrappers the crc32 function with the QuaChecksum32 interface. +* See QuaChecksum32 for more info. +*/ +class QUAZIP_EXPORT QuaCrc32 : public QuaChecksum32 { + +public: + QuaCrc32(); + + quint32 calculate(const QByteArray &data); + + void reset(); + void update(const QByteArray &buf); + quint32 value(); + +private: + quint32 checksum; +}; + +#endif //QUACRC32_H diff --git a/interface/external/quazip/include/quagzipfile.h b/interface/external/quazip/include/quagzipfile.h new file mode 100644 index 0000000000..0a71173824 --- /dev/null +++ b/interface/external/quazip/include/quagzipfile.h @@ -0,0 +1,84 @@ +#ifndef QUAZIP_QUAGZIPFILE_H +#define QUAZIP_QUAGZIPFILE_H + +#include +#include "quazip_global.h" + +#include + +class QuaGzipFilePrivate; + +/// GZIP file +/** + This class is a wrapper around GZIP file access functions in zlib. Unlike QuaZip classes, it doesn't allow reading from a GZIP file opened as QIODevice, for example, if your GZIP file is in QBuffer. It only provides QIODevice access to a GZIP file contents, but the GZIP file itself must be identified by its name on disk or by descriptor id. + */ +class QUAZIP_EXPORT QuaGzipFile: public QIODevice { + Q_OBJECT +public: + /// Empty constructor. + /** + Must call setFileName() before trying to open. + */ + QuaGzipFile(); + /// Empty constructor with a parent. + /** + Must call setFileName() before trying to open. + \param parent The parent object, as per QObject logic. + */ + QuaGzipFile(QObject *parent); + /// Constructor. + /** + \param fileName The name of the GZIP file. + \param parent The parent object, as per QObject logic. + */ + QuaGzipFile(const QString &fileName, QObject *parent = NULL); + /// Destructor. + virtual ~QuaGzipFile(); + /// Sets the name of the GZIP file to be opened. + void setFileName(const QString& fileName); + /// Returns the name of the GZIP file. + QString getFileName() const; + /// Returns true. + /** + Strictly speaking, zlib supports seeking for GZIP files, but it is + poorly implemented, because there is no way to implement it + properly. For reading, seeking backwards is very slow, and for + writing, it is downright impossible. Therefore, QuaGzipFile does not + support seeking at all. + */ + virtual bool isSequential() const; + /// Opens the file. + /** + \param mode Can be either QIODevice::Write or QIODevice::Read. + ReadWrite and Append aren't supported. + */ + virtual bool open(QIODevice::OpenMode mode); + /// Opens the file. + /** + \overload + \param fd The file descriptor to read/write the GZIP file from/to. + \param mode Can be either QIODevice::Write or QIODevice::Read. + ReadWrite and Append aren't supported. + */ + virtual bool open(int fd, QIODevice::OpenMode mode); + /// Flushes data to file. + /** + The data is written using Z_SYNC_FLUSH mode. Doesn't make any sense + when reading. + */ + virtual bool flush(); + /// Closes the file. + virtual void close(); +protected: + /// Implementation of QIODevice::readData(). + virtual qint64 readData(char *data, qint64 maxSize); + /// Implementation of QIODevice::writeData(). + virtual qint64 writeData(const char *data, qint64 maxSize); +private: + // not implemented by design to disable copy + QuaGzipFile(const QuaGzipFile &that); + QuaGzipFile& operator=(const QuaGzipFile &that); + QuaGzipFilePrivate *d; +}; + +#endif // QUAZIP_QUAGZIPFILE_H diff --git a/interface/external/quazip/include/quaziodevice.h b/interface/external/quazip/include/quaziodevice.h new file mode 100644 index 0000000000..ff0d31a5c4 --- /dev/null +++ b/interface/external/quazip/include/quaziodevice.h @@ -0,0 +1,74 @@ +#ifndef QUAZIP_QUAZIODEVICE_H +#define QUAZIP_QUAZIODEVICE_H + +#include +#include "quazip_global.h" + +#include + +class QuaZIODevicePrivate; + +/// A class to compress/decompress QIODevice. +/** + This class can be used to compress any data written to QIODevice or + decompress it back. Compressing data sent over a QTcpSocket is a good + example. + */ +class QUAZIP_EXPORT QuaZIODevice: public QIODevice { + Q_OBJECT +public: + /// Constructor. + /** + \param io The QIODevice to read/write. + \param parent The parent object, as per QObject logic. + */ + QuaZIODevice(QIODevice *io, QObject *parent = NULL); + /// Destructor. + ~QuaZIODevice(); + /// Flushes data waiting to be written. + /** + Unfortunately, as QIODevice doesn't support flush() by itself, the + only thing this method does is write the compressed data into the + device using Z_SYNC_FLUSH mode. If you need the compressed data to + actually be flushed from the buffer of the underlying QIODevice, you + need to call its flush() method as well, providing it supports it + (like QTcpSocket does). Example: + \code + QuaZIODevice dev(&sock); + dev.open(QIODevice::Write); + dev.write(yourDataGoesHere); + dev.flush(); + sock->flush(); // this actually sends data to network + \endcode + + This may change in the future versions of QuaZIP by implementing an + ugly hack: trying to cast the QIODevice using qobject_cast to known + flush()-supporting subclasses, and calling flush if the resulting + pointer is not zero. + */ + virtual bool flush(); + /// Opens the device. + /** + \param mode Neither QIODevice::ReadWrite nor QIODevice::Append are + not supported. + */ + virtual bool open(QIODevice::OpenMode mode); + /// Closes this device, but not the underlying one. + /** + The underlying QIODevice is not closed in case you want to write + something else to it. + */ + virtual void close(); + /// Returns the underlying device. + QIODevice *getIoDevice() const; + /// Returns true. + virtual bool isSequential() const; +protected: + /// Implementation of QIODevice::readData(). + virtual qint64 readData(char *data, qint64 maxSize); + /// Implementation of QIODevice::writeData(). + virtual qint64 writeData(const char *data, qint64 maxSize); +private: + QuaZIODevicePrivate *d; +}; +#endif // QUAZIP_QUAZIODEVICE_H diff --git a/interface/external/quazip/include/quazip.h b/interface/external/quazip/include/quazip.h new file mode 100644 index 0000000000..4daa322b5f --- /dev/null +++ b/interface/external/quazip/include/quazip.h @@ -0,0 +1,419 @@ +#ifndef QUA_ZIP_H +#define QUA_ZIP_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include +#include +#include + +#include "zip.h" +#include "unzip.h" + +#include "quazip_global.h" +#include "quazipfileinfo.h" + +// just in case it will be defined in the later versions of the ZIP/UNZIP +#ifndef UNZ_OPENERROR +// define additional error code +#define UNZ_OPENERROR -1000 +#endif + +class QuaZipPrivate; + +/// ZIP archive. +/** \class QuaZip quazip.h + * This class implements basic interface to the ZIP archive. It can be + * used to read table contents of the ZIP archive and retreiving + * information about the files inside it. + * + * You can also use this class to open files inside archive by passing + * pointer to the instance of this class to the constructor of the + * QuaZipFile class. But see QuaZipFile::QuaZipFile(QuaZip*, QObject*) + * for the possible pitfalls. + * + * This class is indended to provide interface to the ZIP subpackage of + * the ZIP/UNZIP package as well as to the UNZIP subpackage. But + * currently it supports only UNZIP. + * + * The use of this class is simple - just create instance using + * constructor, then set ZIP archive file name using setFile() function + * (if you did not passed the name to the constructor), then open() and + * then use different functions to work with it! Well, if you are + * paranoid, you may also wish to call close before destructing the + * instance, to check for errors on close. + * + * You may also use getUnzFile() and getZipFile() functions to get the + * ZIP archive handle and use it with ZIP/UNZIP package API directly. + * + * This class supports localized file names inside ZIP archive, but you + * have to set up proper codec with setCodec() function. By default, + * locale codec will be used, which is probably ok for UNIX systems, but + * will almost certainly fail with ZIP archives created in Windows. This + * is because Windows ZIP programs have strange habit of using DOS + * encoding for file names in ZIP archives. For example, ZIP archive + * with cyrillic names created in Windows will have file names in \c + * IBM866 encoding instead of \c WINDOWS-1251. I think that calling one + * function is not much trouble, but for true platform independency it + * would be nice to have some mechanism for file name encoding auto + * detection using locale information. Does anyone know a good way to do + * it? + **/ +class QUAZIP_EXPORT QuaZip { + friend class QuaZipPrivate; + public: + /// Useful constants. + enum Constants { + MAX_FILE_NAME_LENGTH=256 /**< Maximum file name length. Taken from + \c UNZ_MAXFILENAMEINZIP constant in + unzip.c. */ + }; + /// Open mode of the ZIP file. + enum Mode { + mdNotOpen, ///< ZIP file is not open. This is the initial mode. + mdUnzip, ///< ZIP file is open for reading files inside it. + mdCreate, ///< ZIP file was created with open() call. + mdAppend, /**< ZIP file was opened in append mode. This refers to + * \c APPEND_STATUS_CREATEAFTER mode in ZIP/UNZIP package + * and means that zip is appended to some existing file + * what is useful when that file contains + * self-extractor code. This is obviously \em not what + * you whant to use to add files to the existing ZIP + * archive. + **/ + mdAdd ///< ZIP file was opened for adding files in the archive. + }; + /// Case sensitivity for the file names. + /** This is what you specify when accessing files in the archive. + * Works perfectly fine with any characters thanks to Qt's great + * unicode support. This is different from ZIP/UNZIP API, where + * only US-ASCII characters was supported. + **/ + enum CaseSensitivity { + csDefault=0, ///< Default for platform. Case sensitive for UNIX, not for Windows. + csSensitive=1, ///< Case sensitive. + csInsensitive=2 ///< Case insensitive. + }; + /// Returns the actual case sensitivity for the specified QuaZIP one. + /** + \param cs The value to convert. + \returns If CaseSensitivity::csDefault, then returns the default + file name case sensitivity for the platform. Otherwise, just + returns the appropriate value from the Qt::CaseSensitivity enum. + */ + static Qt::CaseSensitivity convertCaseSensitivity( + CaseSensitivity cs); + private: + QuaZipPrivate *p; + // not (and will not be) implemented + QuaZip(const QuaZip& that); + // not (and will not be) implemented + QuaZip& operator=(const QuaZip& that); + public: + /// Constructs QuaZip object. + /** Call setName() before opening constructed object. */ + QuaZip(); + /// Constructs QuaZip object associated with ZIP file \a zipName. + QuaZip(const QString& zipName); + /// Constructs QuaZip object associated with ZIP file represented by \a ioDevice. + /** The IO device must be seekable, otherwise an error will occur when opening. */ + QuaZip(QIODevice *ioDevice); + /// Destroys QuaZip object. + /** Calls close() if necessary. */ + ~QuaZip(); + /// Opens ZIP file. + /** + * Argument \a mode specifies open mode of the ZIP archive. See Mode + * for details. Note that there is zipOpen2() function in the + * ZIP/UNZIP API which accepts \a globalcomment argument, but it + * does not use it anywhere, so this open() function does not have this + * argument. See setComment() if you need to set global comment. + * + * If the ZIP file is accessed via explicitly set QIODevice, then + * this device is opened in the necessary mode. If the device was + * already opened by some other means, then the behaviour is defined by + * the device implementation, but generally it is not a very good + * idea. For example, QFile will at least issue a warning. + * + * \return \c true if successful, \c false otherwise. + * + * \note ZIP/UNZIP API open calls do not return error code - they + * just return \c NULL indicating an error. But to make things + * easier, quazip.h header defines additional error code \c + * UNZ_ERROROPEN and getZipError() will return it if the open call + * of the ZIP/UNZIP API returns \c NULL. + * + * Argument \a ioApi specifies IO function set for ZIP/UNZIP + * package to use. See unzip.h, zip.h and ioapi.h for details. Note + * that IO API for QuaZip is different from the original package. + * The file path argument was changed to be of type \c voidpf, and + * QuaZip passes a QIODevice pointer there. This QIODevice is either + * set explicitly via setIoDevice() or the QuaZip(QIODevice*) + * constructor, or it is created internally when opening the archive + * by its file name. The default API (qioapi.cpp) just delegates + * everything to the QIODevice API. Not only this allows to use a + * QIODevice instead of file name, but also has a nice side effect + * of raising the file size limit from 2G to 4G. + * + * In short: just forget about the \a ioApi argument and you'll be + * fine. + **/ + bool open(Mode mode, zlib_filefunc_def *ioApi =NULL); + /// Closes ZIP file. + /** Call getZipError() to determine if the close was successful. The + * underlying QIODevice is also closed, regardless of whether it was + * set explicitly or not. */ + void close(); + /// Sets the codec used to encode/decode file names inside archive. + /** This is necessary to access files in the ZIP archive created + * under Windows with non-latin characters in file names. For + * example, file names with cyrillic letters will be in \c IBM866 + * encoding. + **/ + void setFileNameCodec(QTextCodec *fileNameCodec); + /// Sets the codec used to encode/decode file names inside archive. + /** \overload + * Equivalent to calling setFileNameCodec(QTextCodec::codecForName(codecName)); + **/ + void setFileNameCodec(const char *fileNameCodecName); + /// Returns the codec used to encode/decode comments inside archive. + QTextCodec* getFileNameCodec() const; + /// Sets the codec used to encode/decode comments inside archive. + /** This codec defaults to locale codec, which is probably ok. + **/ + void setCommentCodec(QTextCodec *commentCodec); + /// Sets the codec used to encode/decode comments inside archive. + /** \overload + * Equivalent to calling setCommentCodec(QTextCodec::codecForName(codecName)); + **/ + void setCommentCodec(const char *commentCodecName); + /// Returns the codec used to encode/decode comments inside archive. + QTextCodec* getCommentCodec() const; + /// Returns the name of the ZIP file. + /** Returns null string if no ZIP file name has been set, for + * example when the QuaZip instance is set up to use a QIODevice + * instead. + * \sa setZipName(), setIoDevice(), getIoDevice() + **/ + QString getZipName() const; + /// Sets the name of the ZIP file. + /** Does nothing if the ZIP file is open. + * + * Does not reset error code returned by getZipError(). + * \sa setIoDevice(), getIoDevice(), getZipName() + **/ + void setZipName(const QString& zipName); + /// Returns the device representing this ZIP file. + /** Returns null string if no device has been set explicitly, for + * example when opening a ZIP file by name. + * \sa setIoDevice(), getZipName(), setZipName() + **/ + QIODevice *getIoDevice() const; + /// Sets the device representing the ZIP file. + /** Does nothing if the ZIP file is open. + * + * Does not reset error code returned by getZipError(). + * \sa getIoDevice(), getZipName(), setZipName() + **/ + void setIoDevice(QIODevice *ioDevice); + /// Returns the mode in which ZIP file was opened. + Mode getMode() const; + /// Returns \c true if ZIP file is open, \c false otherwise. + bool isOpen() const; + /// Returns the error code of the last operation. + /** Returns \c UNZ_OK if the last operation was successful. + * + * Error code resets to \c UNZ_OK every time you call any function + * that accesses something inside ZIP archive, even if it is \c + * const (like getEntriesCount()). open() and close() calls reset + * error code too. See documentation for the specific functions for + * details on error detection. + **/ + int getZipError() const; + /// Returns number of the entries in the ZIP central directory. + /** Returns negative error code in the case of error. The same error + * code will be returned by subsequent getZipError() call. + **/ + int getEntriesCount() const; + /// Returns global comment in the ZIP file. + QString getComment() const; + /// Sets the global comment in the ZIP file. + /** The comment will be written to the archive on close operation. + * QuaZip makes a distinction between a null QByteArray() comment + * and an empty "" comment in the QuaZip::mdAdd mode. + * A null comment is the default and it means "don't change + * the comment". An empty comment removes the original comment. + * + * \sa open() + **/ + void setComment(const QString& comment); + /// Sets the current file to the first file in the archive. + /** Returns \c true on success, \c false otherwise. Call + * getZipError() to get the error code. + **/ + bool goToFirstFile(); + /// Sets the current file to the next file in the archive. + /** Returns \c true on success, \c false otherwise. Call + * getZipError() to determine if there was an error. + * + * Should be used only in QuaZip::mdUnzip mode. + * + * \note If the end of file was reached, getZipError() will return + * \c UNZ_OK instead of \c UNZ_END_OF_LIST_OF_FILE. This is to make + * things like this easier: + * \code + * for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { + * // do something + * } + * if(zip.getZipError()==UNZ_OK) { + * // ok, there was no error + * } + * \endcode + **/ + bool goToNextFile(); + /// Sets current file by its name. + /** Returns \c true if successful, \c false otherwise. Argument \a + * cs specifies case sensitivity of the file name. Call + * getZipError() in the case of a failure to get error code. + * + * This is not a wrapper to unzLocateFile() function. That is + * because I had to implement locale-specific case-insensitive + * comparison. + * + * Here are the differences from the original implementation: + * + * - If the file was not found, error code is \c UNZ_OK, not \c + * UNZ_END_OF_LIST_OF_FILE (see also goToNextFile()). + * - If this function fails, it unsets the current file rather than + * resetting it back to what it was before the call. + * + * If \a fileName is null string then this function unsets the + * current file and return \c true. Note that you should close the + * file first if it is open! See + * QuaZipFile::QuaZipFile(QuaZip*,QObject*) for the details. + * + * Should be used only in QuaZip::mdUnzip mode. + * + * \sa setFileNameCodec(), CaseSensitivity + **/ + bool setCurrentFile(const QString& fileName, CaseSensitivity cs =csDefault); + /// Returns \c true if the current file has been set. + bool hasCurrentFile() const; + /// Retrieves information about the current file. + /** Fills the structure pointed by \a info. Returns \c true on + * success, \c false otherwise. In the latter case structure pointed + * by \a info remains untouched. If there was an error, + * getZipError() returns error code. + * + * Should be used only in QuaZip::mdUnzip mode. + * + * Does nothing and returns \c false in any of the following cases. + * - ZIP is not open; + * - ZIP does not have current file; + * - \a info is \c NULL; + * + * In all these cases getZipError() returns \c UNZ_OK since there + * is no ZIP/UNZIP API call. + **/ + bool getCurrentFileInfo(QuaZipFileInfo* info)const; + /// Returns the current file name. + /** Equivalent to calling getCurrentFileInfo() and then getting \c + * name field of the QuaZipFileInfo structure, but faster and more + * convenient. + * + * Should be used only in QuaZip::mdUnzip mode. + **/ + QString getCurrentFileName()const; + /// Returns \c unzFile handle. + /** You can use this handle to directly call UNZIP part of the + * ZIP/UNZIP package functions (see unzip.h). + * + * \warning When using the handle returned by this function, please + * keep in mind that QuaZip class is unable to detect any changes + * you make in the ZIP file state (e. g. changing current file, or + * closing the handle). So please do not do anything with this + * handle that is possible to do with the functions of this class. + * Or at least return the handle in the original state before + * calling some another function of this class (including implicit + * destructor calls and calls from the QuaZipFile objects that refer + * to this QuaZip instance!). So if you have changed the current + * file in the ZIP archive - then change it back or you may + * experience some strange behavior or even crashes. + **/ + unzFile getUnzFile(); + /// Returns \c zipFile handle. + /** You can use this handle to directly call ZIP part of the + * ZIP/UNZIP package functions (see zip.h). Warnings about the + * getUnzFile() function also apply to this function. + **/ + zipFile getZipFile(); + /// Changes the data descriptor writing mode. + /** + According to the ZIP format specification, a file inside archive + may have a data descriptor immediately following the file + data. This is reflected by a special flag in the local file header + and in the central directory. By default, QuaZIP sets this flag + and writes the data descriptor unless both method and level were + set to 0, in which case it operates in 1.0-compatible mode and + never writes data descriptors. + + By setting this flag to false, it is possible to disable data + descriptor writing, thus increasing compatibility with archive + readers that don't understand this feature of the ZIP file format. + + Setting this flag affects all the QuaZipFile instances that are + opened after this flag is set. + + The data descriptor writing mode is enabled by default. + + \param enabled If \c true, enable local descriptor writing, + disable it otherwise. + + \sa QuaZipFile::setDataDescriptorWritingEnabled() + */ + void setDataDescriptorWritingEnabled(bool enabled); + /// Returns the data descriptor default writing mode. + /** + \sa setDataDescriptorWritingEnabled() + */ + bool isDataDescriptorWritingEnabled() const; + /// Returns a list of files inside the archive. + /** + \return A list of file names or an empty list if there + was an error or if the archive is empty (call getZipError() to + figure out which). + \sa getFileInfoList() + */ + QStringList getFileNameList() const; + /// Returns information list about all files inside the archive. + /** + \return A list of QuaZipFileInfo objects or an empty list if there + was an error or if the archive is empty (call getZipError() to + figure out which). + \sa getFileNameList() + */ + QList getFileInfoList() const; +}; + +#endif diff --git a/interface/external/quazip/include/quazip_global.h b/interface/external/quazip/include/quazip_global.h new file mode 100644 index 0000000000..d9d09ade18 --- /dev/null +++ b/interface/external/quazip/include/quazip_global.h @@ -0,0 +1,55 @@ +/** +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + */ + +#ifndef QUAZIP_GLOBAL_H +#define QUAZIP_GLOBAL_H + +#include + +/** + This is automatically defined when building a static library, but when + including QuaZip sources directly into a project, QUAZIP_STATIC should + be defined explicitly to avoid possible troubles with unnecessary + importing/exporting. + */ +#ifdef QUAZIP_STATIC +#define QUAZIP_EXPORT +#else +/** + * When building a DLL with MSVC, QUAZIP_BUILD must be defined. + * qglobal.h takes care of defining Q_DECL_* correctly for msvc/gcc. + */ +#if defined(QUAZIP_BUILD) + #define QUAZIP_EXPORT Q_DECL_EXPORT +#else + #define QUAZIP_EXPORT Q_DECL_IMPORT +#endif +#endif // QUAZIP_STATIC + +#ifdef __GNUC__ +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + +#endif // QUAZIP_GLOBAL_H diff --git a/interface/external/quazip/include/quazipdir.h b/interface/external/quazip/include/quazipdir.h new file mode 100644 index 0000000000..e2d70bc888 --- /dev/null +++ b/interface/external/quazip/include/quazipdir.h @@ -0,0 +1,171 @@ +#ifndef QUAZIP_QUAZIPDIR_H +#define QUAZIP_QUAZIPDIR_H + +class QuaZipDirPrivate; + +#include "quazip.h" +#include "quazipfileinfo.h" +#include +#include +#include + +/// Provides ZIP archive navigation. +/** +* This class is modelled after QDir, and is designed to provide similar +* features for ZIP archives. +* +* The only significant difference from QDir is that the root path is not +* '/', but an empty string since that's how the file paths are stored in +* the archive. However, QuaZipDir understands the paths starting with +* '/'. It is important in a few places: +* +* - In the cd() function. +* - In the constructor. +* - In the exists() function. +* +* Note that since ZIP uses '/' on all platforms, the '\' separator is +* not supported. +*/ +class QUAZIP_EXPORT QuaZipDir { +private: + QSharedDataPointer d; +public: + /// The copy constructor. + QuaZipDir(const QuaZipDir &that); + /// Constructs a QuaZipDir instance pointing to the specified directory. + /** + If \a dir is not specified, points to the root of the archive. + The same happens if the \a dir is "/". + */ + QuaZipDir(QuaZip *zip, const QString &dir = QString()); + /// Destructor. + ~QuaZipDir(); + /// The assignment operator. + bool operator==(const QuaZipDir &that); + /// operator!= + /** + \return \c true if either this and \a that use different QuaZip + instances or if they point to different directories. + */ + inline bool operator!=(const QuaZipDir &that) {return !operator==(that);} + /// operator== + /** + \return \c true if both this and \a that use the same QuaZip + instance and point to the same directory. + */ + QuaZipDir& operator=(const QuaZipDir &that); + /// Returns the name of the entry at the specified position. + QString operator[](int pos) const; + /// Returns the current case sensitivity mode. + QuaZip::CaseSensitivity caseSensitivity() const; + /// Changes the 'current' directory. + /** + * If the path starts with '/', it is interpreted as an absolute + * path from the root of the archive. Otherwise, it is interpreted + * as a path relative to the current directory as was set by the + * previous cd() or the constructor. + * + * Note that the subsequent path() call will not return a path + * starting with '/' in all cases. + */ + bool cd(const QString &dirName); + /// Goes up. + bool cdUp(); + /// Returns the number of entries in the directory. + uint count() const; + /// Returns the current directory name. + /** + The name doesn't include the path. + */ + QString dirName() const; + /// Returns the list of the entries in the directory. + /** + \param nameFilters The list of file patterns to list, uses the same + syntax as QDir. + \param filters The entry type filters, only Files and Dirs are + accepted. + \param sort Sorting mode (not supported yet). + */ + QList entryInfoList(const QStringList &nameFilters, + QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns the list of the entries in the directory. + /** + \overload + + The same as entryInfoList(QStringList(), filters, sort). + */ + QList entryInfoList(QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns the list of the entry names in the directory. + /** + The same as entryInfoList(nameFilters, filters, sort), but only + returns entry names. + */ + QStringList entryList(const QStringList &nameFilters, + QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns the list of the entry names in the directory. + /** + \overload + + The same as entryList(QStringList(), filters, sort). + */ + QStringList entryList(QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns \c true if the entry with the specified name exists. + /** + The ".." is considered to exist if the current directory + is not root. The "." and "/" are considered to + always exist. Paths starting with "/" are relative to + the archive root, other paths are relative to the current dir. + */ + bool exists(const QString &fileName) const; + /// Return \c true if the directory pointed by this QuaZipDir exists. + bool exists() const; + /// Returns the full path to the specified file. + /** + Doesn't check if the file actually exists. + */ + QString filePath(const QString &fileName) const; + /// Returns the default filter. + QDir::Filters filter(); + /// Returns if the QuaZipDir points to the root of the archive. + /** + Not that the root path is the empty string, not '/'. + */ + bool isRoot() const; + /// Return the default name filter. + QStringList nameFilters() const; + /// Returns the path to the current dir. + /** + The path never starts with '/', and the root path is an empty + string. + */ + QString path() const; + /// Returns the path to the specified file relative to the current dir. + QString relativeFilePath(const QString &fileName) const; + /// Sets the default case sensitivity mode. + void setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity); + /// Sets the default filter. + void setFilter(QDir::Filters filters); + /// Sets the default name filter. + void setNameFilters(const QStringList &nameFilters); + /// Goes to the specified path. + /** + The difference from cd() is that this function never checks if the + path actually exists and doesn't use relative paths, so it's + possible to go to the root directory with setPath(""). + + Note that this function still chops the trailing and/or leading + '/' and treats a single '/' as the root path (path() will still + return an empty string). + */ + void setPath(const QString &path); + /// Sets the default sorting mode. + void setSorting(QDir::SortFlags sort); + /// Returns the default sorting mode. + QDir::SortFlags sorting() const; +}; + +#endif // QUAZIP_QUAZIPDIR_H diff --git a/interface/external/quazip/include/quazipfile.h b/interface/external/quazip/include/quazipfile.h new file mode 100644 index 0000000000..f6cc41a6bc --- /dev/null +++ b/interface/external/quazip/include/quazipfile.h @@ -0,0 +1,442 @@ +#ifndef QUA_ZIPFILE_H +#define QUA_ZIPFILE_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include + +#include "quazip_global.h" +#include "quazip.h" +#include "quazipnewinfo.h" + +class QuaZipFilePrivate; + +/// A file inside ZIP archive. +/** \class QuaZipFile quazipfile.h + * This is the most interesting class. Not only it provides C++ + * interface to the ZIP/UNZIP package, but also integrates it with Qt by + * subclassing QIODevice. This makes possible to access files inside ZIP + * archive using QTextStream or QDataStream, for example. Actually, this + * is the main purpose of the whole QuaZIP library. + * + * You can either use existing QuaZip instance to create instance of + * this class or pass ZIP archive file name to this class, in which case + * it will create internal QuaZip object. See constructors' descriptions + * for details. Writing is only possible with the existing instance. + * + * Note that due to the underlying library's limitation it is not + * possible to use multiple QuaZipFile instances to open several files + * in the same archive at the same time. If you need to write to + * multiple files in parallel, then you should write to temporary files + * first, then pack them all at once when you have finished writing. If + * you need to read multiple files inside the same archive in parallel, + * you should extract them all into a temporary directory first. + * + * \section quazipfile-sequential Sequential or random-access? + * + * At the first thought, QuaZipFile has fixed size, the start and the + * end and should be therefore considered random-access device. But + * there is one major obstacle to making it random-access: ZIP/UNZIP API + * does not support seek() operation and the only way to implement it is + * through reopening the file and re-reading to the required position, + * but this is prohibitively slow. + * + * Therefore, QuaZipFile is considered to be a sequential device. This + * has advantage of availability of the ungetChar() operation (QIODevice + * does not implement it properly for non-sequential devices unless they + * support seek()). Disadvantage is a somewhat strange behaviour of the + * size() and pos() functions. This should be kept in mind while using + * this class. + * + **/ +class QUAZIP_EXPORT QuaZipFile: public QIODevice { + friend class QuaZipFilePrivate; + Q_OBJECT + private: + QuaZipFilePrivate *p; + // these are not supported nor implemented + QuaZipFile(const QuaZipFile& that); + QuaZipFile& operator=(const QuaZipFile& that); + protected: + /// Implementation of the QIODevice::readData(). + qint64 readData(char *data, qint64 maxSize); + /// Implementation of the QIODevice::writeData(). + qint64 writeData(const char *data, qint64 maxSize); + public: + /// Constructs a QuaZipFile instance. + /** You should use setZipName() and setFileName() or setZip() before + * trying to call open() on the constructed object. + **/ + QuaZipFile(); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object. + * + * You should use setZipName() and setFileName() or setZip() before + * trying to call open() on the constructed object. + **/ + QuaZipFile(QObject *parent); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object and \a + * zipName specifies ZIP archive file name. + * + * You should use setFileName() before trying to call open() on the + * constructed object. + * + * QuaZipFile constructed by this constructor can be used for read + * only access. Use QuaZipFile(QuaZip*,QObject*) for writing. + **/ + QuaZipFile(const QString& zipName, QObject *parent =NULL); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object, \a + * zipName specifies ZIP archive file name and \a fileName and \a cs + * specify a name of the file to open inside archive. + * + * QuaZipFile constructed by this constructor can be used for read + * only access. Use QuaZipFile(QuaZip*,QObject*) for writing. + * + * \sa QuaZip::setCurrentFile() + **/ + QuaZipFile(const QString& zipName, const QString& fileName, + QuaZip::CaseSensitivity cs =QuaZip::csDefault, QObject *parent =NULL); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object. + * + * \a zip is the pointer to the existing QuaZip object. This + * QuaZipFile object then can be used to read current file in the + * \a zip or to write to the file inside it. + * + * \warning Using this constructor for reading current file can be + * tricky. Let's take the following example: + * \code + * QuaZip zip("archive.zip"); + * zip.open(QuaZip::mdUnzip); + * zip.setCurrentFile("file-in-archive"); + * QuaZipFile file(&zip); + * file.open(QIODevice::ReadOnly); + * // ok, now we can read from the file + * file.read(somewhere, some); + * zip.setCurrentFile("another-file-in-archive"); // oops... + * QuaZipFile anotherFile(&zip); + * anotherFile.open(QIODevice::ReadOnly); + * anotherFile.read(somewhere, some); // this is still ok... + * file.read(somewhere, some); // and this is NOT + * \endcode + * So, what exactly happens here? When we change current file in the + * \c zip archive, \c file that references it becomes invalid + * (actually, as far as I understand ZIP/UNZIP sources, it becomes + * closed, but QuaZipFile has no means to detect it). + * + * Summary: do not close \c zip object or change its current file as + * long as QuaZipFile is open. Even better - use another constructors + * which create internal QuaZip instances, one per object, and + * therefore do not cause unnecessary trouble. This constructor may + * be useful, though, if you already have a QuaZip instance and do + * not want to access several files at once. Good example: + * \code + * QuaZip zip("archive.zip"); + * zip.open(QuaZip::mdUnzip); + * // first, we need some information about archive itself + * QByteArray comment=zip.getComment(); + * // and now we are going to access files inside it + * QuaZipFile file(&zip); + * for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { + * file.open(QIODevice::ReadOnly); + * // do something cool with file here + * file.close(); // do not forget to close! + * } + * zip.close(); + * \endcode + **/ + QuaZipFile(QuaZip *zip, QObject *parent =NULL); + /// Destroys a QuaZipFile instance. + /** Closes file if open, destructs internal QuaZip object (if it + * exists and \em is internal, of course). + **/ + virtual ~QuaZipFile(); + /// Returns the ZIP archive file name. + /** If this object was created by passing QuaZip pointer to the + * constructor, this function will return that QuaZip's file name + * (or null string if that object does not have file name yet). + * + * Otherwise, returns associated ZIP archive file name or null + * string if there are no name set yet. + * + * \sa setZipName() getFileName() + **/ + QString getZipName()const; + /// Returns a pointer to the associated QuaZip object. + /** Returns \c NULL if there is no associated QuaZip or it is + * internal (so you will not mess with it). + **/ + QuaZip* getZip()const; + /// Returns file name. + /** This function returns file name you passed to this object either + * by using + * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*) + * or by calling setFileName(). Real name of the file may differ in + * case if you used case-insensitivity. + * + * Returns null string if there is no file name set yet. This is the + * case when this QuaZipFile operates on the existing QuaZip object + * (constructor QuaZipFile(QuaZip*,QObject*) or setZip() was used). + * + * \sa getActualFileName + **/ + QString getFileName() const; + /// Returns case sensitivity of the file name. + /** This function returns case sensitivity argument you passed to + * this object either by using + * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*) + * or by calling setFileName(). + * + * Returns unpredictable value if getFileName() returns null string + * (this is the case when you did not used setFileName() or + * constructor above). + * + * \sa getFileName + **/ + QuaZip::CaseSensitivity getCaseSensitivity() const; + /// Returns the actual file name in the archive. + /** This is \em not a ZIP archive file name, but a name of file inside + * archive. It is not necessary the same name that you have passed + * to the + * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*), + * setFileName() or QuaZip::setCurrentFile() - this is the real file + * name inside archive, so it may differ in case if the file name + * search was case-insensitive. + * + * Equivalent to calling getCurrentFileName() on the associated + * QuaZip object. Returns null string if there is no associated + * QuaZip object or if it does not have a current file yet. And this + * is the case if you called setFileName() but did not open the + * file yet. So this is perfectly fine: + * \code + * QuaZipFile file("somezip.zip"); + * file.setFileName("somefile"); + * QString name=file.getName(); // name=="somefile" + * QString actual=file.getActualFileName(); // actual is null string + * file.open(QIODevice::ReadOnly); + * QString actual=file.getActualFileName(); // actual can be "SoMeFiLe" on Windows + * \endcode + * + * \sa getZipName(), getFileName(), QuaZip::CaseSensitivity + **/ + QString getActualFileName()const; + /// Sets the ZIP archive file name. + /** Automatically creates internal QuaZip object and destroys + * previously created internal QuaZip object, if any. + * + * Will do nothing if this file is already open. You must close() it + * first. + **/ + void setZipName(const QString& zipName); + /// Returns \c true if the file was opened in raw mode. + /** If the file is not open, the returned value is undefined. + * + * \sa open(OpenMode,int*,int*,bool,const char*) + **/ + bool isRaw() const; + /// Binds to the existing QuaZip instance. + /** This function destroys internal QuaZip object, if any, and makes + * this QuaZipFile to use current file in the \a zip object for any + * further operations. See QuaZipFile(QuaZip*,QObject*) for the + * possible pitfalls. + * + * Will do nothing if the file is currently open. You must close() + * it first. + **/ + void setZip(QuaZip *zip); + /// Sets the file name. + /** Will do nothing if at least one of the following conditions is + * met: + * - ZIP name has not been set yet (getZipName() returns null + * string). + * - This QuaZipFile is associated with external QuaZip. In this + * case you should call that QuaZip's setCurrentFile() function + * instead! + * - File is already open so setting the name is meaningless. + * + * \sa QuaZip::setCurrentFile + **/ + void setFileName(const QString& fileName, QuaZip::CaseSensitivity cs =QuaZip::csDefault); + /// Opens a file for reading. + /** Returns \c true on success, \c false otherwise. + * Call getZipError() to get error code. + * + * \note Since ZIP/UNZIP API provides buffered reading only, + * QuaZipFile does not support unbuffered reading. So do not pass + * QIODevice::Unbuffered flag in \a mode, or open will fail. + **/ + virtual bool open(OpenMode mode); + /// Opens a file for reading. + /** \overload + * Argument \a password specifies a password to decrypt the file. If + * it is NULL then this function behaves just like open(OpenMode). + **/ + inline bool open(OpenMode mode, const char *password) + {return open(mode, NULL, NULL, false, password);} + /// Opens a file for reading. + /** \overload + * Argument \a password specifies a password to decrypt the file. + * + * An integers pointed by \a method and \a level will receive codes + * of the compression method and level used. See unzip.h. + * + * If raw is \c true then no decompression is performed. + * + * \a method should not be \c NULL. \a level can be \c NULL if you + * don't want to know the compression level. + **/ + bool open(OpenMode mode, int *method, int *level, bool raw, const char *password =NULL); + /// Opens a file for writing. + /** \a info argument specifies information about file. It should at + * least specify a correct file name. Also, it is a good idea to + * specify correct timestamp (by default, current time will be + * used). See QuaZipNewInfo. + * + * The \a password argument specifies the password for crypting. Pass NULL + * if you don't need any crypting. The \a crc argument was supposed + * to be used for crypting too, but then it turned out that it's + * false information, so you need to set it to 0 unless you want to + * use the raw mode (see below). + * + * Arguments \a method and \a level specify compression method and + * level. The only method supported is Z_DEFLATED, but you may also + * specify 0 for no compression. If all of the files in the archive + * use both method 0 and either level 0 is explicitly specified or + * data descriptor writing is disabled with + * QuaZip::setDataDescriptorWritingEnabled(), then the + * resulting archive is supposed to be compatible with the 1.0 ZIP + * format version, should you need that. Except for this, \a level + * has no other effects with method 0. + * + * If \a raw is \c true, no compression is performed. In this case, + * \a crc and uncompressedSize field of the \a info are required. + * + * Arguments \a windowBits, \a memLevel, \a strategy provide zlib + * algorithms tuning. See deflateInit2() in zlib. + **/ + bool open(OpenMode mode, const QuaZipNewInfo& info, + const char *password =NULL, quint32 crc =0, + int method =Z_DEFLATED, int level =Z_DEFAULT_COMPRESSION, bool raw =false, + int windowBits =-MAX_WBITS, int memLevel =DEF_MEM_LEVEL, int strategy =Z_DEFAULT_STRATEGY); + /// Returns \c true, but \ref quazipfile-sequential "beware"! + virtual bool isSequential()const; + /// Returns current position in the file. + /** Implementation of the QIODevice::pos(). When reading, this + * function is a wrapper to the ZIP/UNZIP unztell(), therefore it is + * unable to keep track of the ungetChar() calls (which is + * non-virtual and therefore is dangerous to reimplement). So if you + * are using ungetChar() feature of the QIODevice, this function + * reports incorrect value until you get back characters which you + * ungot. + * + * When writing, pos() returns number of bytes already written + * (uncompressed unless you use raw mode). + * + * \note Although + * \ref quazipfile-sequential "QuaZipFile is a sequential device" + * and therefore pos() should always return zero, it does not, + * because it would be misguiding. Keep this in mind. + * + * This function returns -1 if the file or archive is not open. + * + * Error code returned by getZipError() is not affected by this + * function call. + **/ + virtual qint64 pos()const; + /// Returns \c true if the end of file was reached. + /** This function returns \c false in the case of error. This means + * that you called this function on either not open file, or a file + * in the not open archive or even on a QuaZipFile instance that + * does not even have QuaZip instance associated. Do not do that + * because there is no means to determine whether \c false is + * returned because of error or because end of file was reached. + * Well, on the other side you may interpret \c false return value + * as "there is no file open to check for end of file and there is + * no end of file therefore". + * + * When writing, this function always returns \c true (because you + * are always writing to the end of file). + * + * Error code returned by getZipError() is not affected by this + * function call. + **/ + virtual bool atEnd()const; + /// Returns file size. + /** This function returns csize() if the file is open for reading in + * raw mode, usize() if it is open for reading in normal mode and + * pos() if it is open for writing. + * + * Returns -1 on error, call getZipError() to get error code. + * + * \note This function returns file size despite that + * \ref quazipfile-sequential "QuaZipFile is considered to be sequential device", + * for which size() should return bytesAvailable() instead. But its + * name would be very misguiding otherwise, so just keep in mind + * this inconsistence. + **/ + virtual qint64 size()const; + /// Returns compressed file size. + /** Equivalent to calling getFileInfo() and then getting + * compressedSize field, but more convenient and faster. + * + * File must be open for reading before calling this function. + * + * Returns -1 on error, call getZipError() to get error code. + **/ + qint64 csize()const; + /// Returns uncompressed file size. + /** Equivalent to calling getFileInfo() and then getting + * uncompressedSize field, but more convenient and faster. See + * getFileInfo() for a warning. + * + * File must be open for reading before calling this function. + * + * Returns -1 on error, call getZipError() to get error code. + **/ + qint64 usize()const; + /// Gets information about current file. + /** This function does the same thing as calling + * QuaZip::getCurrentFileInfo() on the associated QuaZip object, + * but you can not call getCurrentFileInfo() if the associated + * QuaZip is internal (because you do not have access to it), while + * you still can call this function in that case. + * + * File must be open for reading before calling this function. + * + * Returns \c false in the case of an error. + **/ + bool getFileInfo(QuaZipFileInfo *info); + /// Closes the file. + /** Call getZipError() to determine if the close was successful. + **/ + virtual void close(); + /// Returns the error code returned by the last ZIP/UNZIP API call. + int getZipError() const; + /// Returns the number of bytes available for reading. + virtual qint64 bytesAvailable() const; +}; + +#endif diff --git a/interface/external/quazip/include/quazipfileinfo.h b/interface/external/quazip/include/quazipfileinfo.h new file mode 100644 index 0000000000..327425f13e --- /dev/null +++ b/interface/external/quazip/include/quazipfileinfo.h @@ -0,0 +1,73 @@ +#ifndef QUA_ZIPFILEINFO_H +#define QUA_ZIPFILEINFO_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include +#include +#include + +#include "quazip_global.h" + +/// Information about a file inside archive. +/** Call QuaZip::getCurrentFileInfo() or QuaZipFile::getFileInfo() to + * fill this structure. */ +struct QUAZIP_EXPORT QuaZipFileInfo { + /// File name. + QString name; + /// Version created by. + quint16 versionCreated; + /// Version needed to extract. + quint16 versionNeeded; + /// General purpose flags. + quint16 flags; + /// Compression method. + quint16 method; + /// Last modification date and time. + QDateTime dateTime; + /// CRC. + quint32 crc; + /// Compressed file size. + quint32 compressedSize; + /// Uncompressed file size. + quint32 uncompressedSize; + /// Disk number start. + quint16 diskNumberStart; + /// Internal file attributes. + quint16 internalAttr; + /// External file attributes. + quint32 externalAttr; + /// Comment. + QString comment; + /// Extra field. + QByteArray extra; + /// Get the file permissions. + /** + Returns the high 16 bits of external attributes converted to + QFile::Permissions. + */ + QFile::Permissions getPermissions() const; +}; + +#endif diff --git a/interface/external/quazip/include/quazipnewinfo.h b/interface/external/quazip/include/quazipnewinfo.h new file mode 100644 index 0000000000..cd321c2712 --- /dev/null +++ b/interface/external/quazip/include/quazipnewinfo.h @@ -0,0 +1,121 @@ +#ifndef QUA_ZIPNEWINFO_H +#define QUA_ZIPNEWINFO_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include +#include + +#include "quazip_global.h" + +/// Information about a file to be created. +/** This structure holds information about a file to be created inside + * ZIP archive. At least name should be set to something correct before + * passing this structure to + * QuaZipFile::open(OpenMode,const QuaZipNewInfo&,int,int,bool). + **/ +struct QUAZIP_EXPORT QuaZipNewInfo { + /// File name. + /** This field holds file name inside archive, including path relative + * to archive root. + **/ + QString name; + /// File timestamp. + /** This is the last file modification date and time. Will be stored + * in the archive central directory. It is a good practice to set it + * to the source file timestamp instead of archive creating time. Use + * setFileDateTime() or QuaZipNewInfo(const QString&, const QString&). + **/ + QDateTime dateTime; + /// File internal attributes. + quint16 internalAttr; + /// File external attributes. + /** + The highest 16 bits contain Unix file permissions and type (dir or + file). The constructor QuaZipNewInfo(const QString&, const QString&) + takes permissions from the provided file. + */ + quint32 externalAttr; + /// File comment. + /** Will be encoded using QuaZip::getCommentCodec(). + **/ + QString comment; + /// File local extra field. + QByteArray extraLocal; + /// File global extra field. + QByteArray extraGlobal; + /// Uncompressed file size. + /** This is only needed if you are using raw file zipping mode, i. e. + * adding precompressed file in the zip archive. + **/ + ulong uncompressedSize; + /// Constructs QuaZipNewInfo instance. + /** Initializes name with \a name, dateTime with current date and + * time. Attributes are initialized with zeros, comment and extra + * field with null values. + **/ + QuaZipNewInfo(const QString& name); + /// Constructs QuaZipNewInfo instance. + /** Initializes name with \a name. Timestamp and permissions are taken + * from the specified file. If the \a file does not exists or its timestamp + * is inaccessible (e. g. you do not have read permission for the + * directory file in), uses current time and zero permissions. Other attributes are + * initialized with zeros, comment and extra field with null values. + * + * \sa setFileDateTime() + **/ + QuaZipNewInfo(const QString& name, const QString& file); + /// Sets the file timestamp from the existing file. + /** Use this function to set the file timestamp from the existing + * file. Use it like this: + * \code + * QuaZipFile zipFile(&zip); + * QFile file("file-to-add"); + * file.open(QIODevice::ReadOnly); + * QuaZipNewInfo info("file-name-in-archive"); + * info.setFileDateTime("file-to-add"); // take the timestamp from file + * zipFile.open(QIODevice::WriteOnly, info); + * \endcode + * + * This function does not change dateTime if some error occured (e. g. + * file is inaccessible). + **/ + void setFileDateTime(const QString& file); + /// Sets the file permissions from the existing file. + /** + Takes permissions from the file and sets the high 16 bits of + external attributes. Uses QFileInfo to get permissions on all + platforms. + */ + void setFilePermissions(const QString &file); + /// Sets the file permissions. + /** + Modifies the highest 16 bits of external attributes. The type part + is set to dir if the name ends with a slash, and to regular file + otherwise. + */ + void setPermissions(QFile::Permissions permissions); +}; + +#endif diff --git a/interface/external/quazip/include/unzip.h b/interface/external/quazip/include/unzip.h new file mode 100644 index 0000000000..33c9dc1ab3 --- /dev/null +++ b/interface/external/quazip/include/unzip.h @@ -0,0 +1,356 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Modified by Sergey A. Tachenov to integrate with Qt. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _unz_H +#define _unz_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((voidpf file)); +/* + Open a Zip file. path contain whatever zopen_file from the IO API + accepts. For Qt implementation it is a pointer to QIODevice, for + fopen() implementation it's a file name. + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern unzFile ZEXPORT unzOpen2 OF((voidpf file, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz_H */ diff --git a/interface/external/quazip/include/zip.h b/interface/external/quazip/include/zip.h new file mode 100644 index 0000000000..269ec2dace --- /dev/null +++ b/interface/external/quazip/include/zip.h @@ -0,0 +1,245 @@ +/* zip.h -- IO for compress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow creates .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + For uncompress .zip file, look at unzip.h + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.html for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Modified by Sergey A. Tachenov to integrate with Qt. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _zip_H +#define _zip_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagzipFile__ { int unused; } zipFile__; +typedef zipFile__ *zipFile; +#else +typedef voidp zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#define ZIP_WRITE_DATA_DESCRIPTOR 0x8u + +#ifndef DEF_MEM_LEVEL +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif +/* default memLevel */ + +/* tm_zip contain date/time info */ +typedef struct tm_zip_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_zip; + +typedef struct +{ + tm_zip tmz_date; /* date in understandable format */ + uLong dosDate; /* if dos_date == 0, tmu_date is used */ +/* uLong flag; */ /* general purpose bit flag 2 bytes */ + + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +typedef const char* zipcharpc; + + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +extern zipFile ZEXPORT zipOpen OF((voidpf file, int append)); +/* + Create a zipfile. + file is whatever the IO API accepts. For Qt IO API it's a pointer to + QIODevice. For fopen() IO API it's a file name (const char*). + if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. + (useful if the file contain a self extractor code) + if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will + add files in existing zip (be sure you don't add file that doesn't exist) + If the zipfile cannot be opened, the return value is NULL. + Else, the return value is a zipFile Handle, usable with other function + of this zip package. +*/ + +/* Note : there is no delete function into a zipfile. + If you want delete file into a zipfile, you must open a zipfile, and create another + Of couse, you can use RAW reading and writing to copy the file you did not want delte +*/ + +extern zipFile ZEXPORT zipOpen2 OF((voidpf file, + int append, + zipcharpc* globalcomment, + zlib_filefunc_def* pzlib_filefunc_def)); + +extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level)); +/* + Open a file in the ZIP for writing. + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local + contains the extrafield data the the local header + if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global + contains the extrafield data the the local header + if comment != NULL, comment contain the comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) +*/ + + +extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw)); + +/* + Same than zipOpenNewFileInZip, except if raw=1, we write raw file + */ + +extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCtypting)); + +/* + Same than zipOpenNewFileInZip2, except + windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crcForCtypting : crc of file to compress (needed for crypting) + */ + + +extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, + const void* buf, + unsigned len)); +/* + Write data in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); +/* + Close the current file in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, + uLong uncompressed_size, + uLong crc32)); +/* + Close the current file in the zipfile, for fiel opened with + parameter raw=1 in zipOpenNewFileInZip2 + uncompressed_size and crc32 are value for the uncompressed size +*/ + +extern int ZEXPORT zipClose OF((zipFile file, + const char* global_comment)); +/* + Close the zipfile +*/ + +/* + Added by Sergey A. Tachenov to tweak zipping behaviour. + */ +extern int ZEXPORT zipSetFlags(zipFile file, unsigned flags); +extern int ZEXPORT zipClearFlags(zipFile file, unsigned flags); + +#ifdef __cplusplus +} +#endif + +#endif /* _zip_H */ diff --git a/interface/external/quazip/lib/MacOS/libquazip.a b/interface/external/quazip/lib/MacOS/libquazip.a new file mode 100644 index 0000000000000000000000000000000000000000..2c7b66f5d19e7688914715e7d24d62b912260c03 GIT binary patch literal 230752 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è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 Nu^WSI=<6`-{6EnMkPZL< literal 0 HcmV?d00001 From 5cf89e90d7d28a6b913c85cc8dff885b36d29ee3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Jun 2013 16:45:14 -0700 Subject: [PATCH 02/10] fix redundancy in reference to Rect for OpenCV --- interface/src/Webcam.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 586e806ce3..1b370f6c4b 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -186,7 +186,7 @@ FrameGrabber::~FrameGrabber() { } void FrameGrabber::reset() { - _searchWindow = Rect(0, 0, 0, 0); + _searchWindow = cv::Rect(0, 0, 0, 0); } void FrameGrabber::grabFrame() { @@ -235,7 +235,7 @@ void FrameGrabber::grabFrame() { float ranges[] = { 0, 180 }; const float* range = ranges; if (_searchWindow.area() == 0) { - vector faces; + vector faces; _faceCascade.detectMultiScale(frame, faces, 1.1, 6); if (!faces.empty()) { _searchWindow = faces.front(); From 800bfd097b85dea36010dcf219ed858778f924d6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Jun 2013 10:35:17 -0700 Subject: [PATCH 03/10] alphabetize some includes in Application header --- interface/src/Application.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index 8667e68356..51bafa683a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -9,9 +9,9 @@ #ifndef __interface__Application__ #define __interface__Application__ +#include #include #include -#include #include #include From 0b243ec881efceb555d6d971444cccf2365edb97 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Jun 2013 10:36:32 -0700 Subject: [PATCH 04/10] add fervor updater and run during launch if OS X release --- interface/CMakeLists.txt | 15 +- interface/external/fervor/CMakeLists.txt | 23 + .../external/fervor/fvavailableupdate.cpp | 97 ++ interface/external/fervor/fvavailableupdate.h | 51 + .../external/fervor/fvignoredversions.cpp | 74 ++ interface/external/fervor/fvignoredversions.h | 19 + interface/external/fervor/fvplatform.cpp | 210 ++++ interface/external/fervor/fvplatform.h | 18 + .../fervor/fvupdatedownloadprogress.cpp | 26 + .../fervor/fvupdatedownloadprogress.h | 23 + .../fervor/fvupdatedownloadprogress.ui | 94 ++ interface/external/fervor/fvupdater.cpp | 908 ++++++++++++++++++ interface/external/fervor/fvupdater.h | 183 ++++ interface/external/fervor/fvupdatewindow.cpp | 66 ++ interface/external/fervor/fvupdatewindow.h | 37 + interface/external/fervor/fvupdatewindow.ui | 129 +++ .../external/fervor/fvversioncomparator.cpp | 164 ++++ .../external/fervor/fvversioncomparator.h | 36 + interface/resources/info/ApplicationInfo.ini | 5 + interface/src/Application.cpp | 27 +- 20 files changed, 2199 insertions(+), 6 deletions(-) create mode 100644 interface/external/fervor/CMakeLists.txt create mode 100755 interface/external/fervor/fvavailableupdate.cpp create mode 100755 interface/external/fervor/fvavailableupdate.h create mode 100755 interface/external/fervor/fvignoredversions.cpp create mode 100755 interface/external/fervor/fvignoredversions.h create mode 100755 interface/external/fervor/fvplatform.cpp create mode 100755 interface/external/fervor/fvplatform.h create mode 100755 interface/external/fervor/fvupdatedownloadprogress.cpp create mode 100755 interface/external/fervor/fvupdatedownloadprogress.h create mode 100755 interface/external/fervor/fvupdatedownloadprogress.ui create mode 100755 interface/external/fervor/fvupdater.cpp create mode 100755 interface/external/fervor/fvupdater.h create mode 100755 interface/external/fervor/fvupdatewindow.cpp create mode 100755 interface/external/fervor/fvupdatewindow.h create mode 100755 interface/external/fervor/fvupdatewindow.ui create mode 100755 interface/external/fervor/fvversioncomparator.cpp create mode 100755 interface/external/fervor/fvversioncomparator.h create mode 100644 interface/resources/info/ApplicationInfo.ini diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 148e6819be..f736101830 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -65,10 +65,14 @@ if (APPLE) endif (APPLE) -find_package(Qt4 REQUIRED QtCore QtGui QtNetwork QtOpenGL) +find_package(Qt4 REQUIRED QtCore QtGui QtNetwork QtOpenGL QtWebKit) include(${QT_USE_FILE}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}") +set(QUAZIP_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/quazip) +add_subdirectory(external/fervor/) +include_directories(external/fervor/) + # run qt moc on qt-enabled headers qt4_wrap_cpp(INTERFACE_SRCS src/Application.h src/AvatarVoxelSystem.h src/Webcam.h) @@ -108,7 +112,14 @@ include_directories( ${SPEEXDSP_INCLUDE_DIRS} ) -target_link_libraries(${TARGET_NAME} ${QT_LIBRARIES} ${OPENCV_LIBRARIES} ${ZLIB_LIBRARIES} ${SPEEXDSP_LIBRARIES}) +target_link_libraries( + ${TARGET_NAME} + ${QT_LIBRARIES} + ${OPENCV_LIBRARIES} + ${ZLIB_LIBRARIES} + ${SPEEXDSP_LIBRARIES} + fervor +) if (APPLE) # link in required OS X frameworks and include the right GL headers diff --git a/interface/external/fervor/CMakeLists.txt b/interface/external/fervor/CMakeLists.txt new file mode 100644 index 0000000000..9afeb6f4ec --- /dev/null +++ b/interface/external/fervor/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.8) +project(Fervor) +find_package(Qt4 REQUIRED) + +add_definitions(-DFV_GUI) + +file(GLOB FERVOR_SOURCES *.cpp) +file(GLOB FERVOR_HEADERS *.h) +file(GLOB FERVOR_UI *.ui) + +qt4_wrap_ui(FERVOR_WRAPPED_UI ${FERVOR_UI}) +qt4_wrap_cpp(FERVOR_MOC_SOURCES ${FERVOR_HEADERS}) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") +find_package(Quazip REQUIRED) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${QUAZIP_INCLUDE_DIRS} +) + +add_library(fervor ${FERVOR_SOURCES} ${FERVOR_HEADERS} ${FERVOR_MOC_SOURCES} ${FERVOR_WRAPPED_UI}) +target_link_libraries(fervor ${QUAZIP_LIBRARIES}) \ No newline at end of file diff --git a/interface/external/fervor/fvavailableupdate.cpp b/interface/external/fervor/fvavailableupdate.cpp new file mode 100755 index 0000000000..f0f6cb91cd --- /dev/null +++ b/interface/external/fervor/fvavailableupdate.cpp @@ -0,0 +1,97 @@ +#include "fvavailableupdate.h" + +FvAvailableUpdate::FvAvailableUpdate(QObject *parent) : + QObject(parent) +{ + // noop +} + +QString FvAvailableUpdate::GetTitle() +{ + return m_title; +} + +void FvAvailableUpdate::SetTitle(QString title) +{ + m_title = title; +} + +QUrl FvAvailableUpdate::GetReleaseNotesLink() +{ + return m_releaseNotesLink; +} + +void FvAvailableUpdate::SetReleaseNotesLink(QUrl releaseNotesLink) +{ + m_releaseNotesLink = releaseNotesLink; +} + +void FvAvailableUpdate::SetReleaseNotesLink(QString releaseNotesLink) +{ + SetReleaseNotesLink(QUrl(releaseNotesLink)); +} + +QString FvAvailableUpdate::GetPubDate() +{ + return m_pubDate; +} + +void FvAvailableUpdate::SetPubDate(QString pubDate) +{ + m_pubDate = pubDate; +} + +QUrl FvAvailableUpdate::GetEnclosureUrl() +{ + return m_enclosureUrl; +} + +void FvAvailableUpdate::SetEnclosureUrl(QUrl enclosureUrl) +{ + m_enclosureUrl = enclosureUrl; +} + +void FvAvailableUpdate::SetEnclosureUrl(QString enclosureUrl) +{ + SetEnclosureUrl(QUrl(enclosureUrl)); +} + +QString FvAvailableUpdate::GetEnclosureVersion() +{ + return m_enclosureVersion; +} + +void FvAvailableUpdate::SetEnclosureVersion(QString enclosureVersion) +{ + m_enclosureVersion = enclosureVersion; +} + +QString FvAvailableUpdate::GetEnclosurePlatform() +{ + return m_enclosurePlatform; +} + +void FvAvailableUpdate::SetEnclosurePlatform(QString enclosurePlatform) +{ + m_enclosurePlatform = enclosurePlatform; +} + +unsigned long FvAvailableUpdate::GetEnclosureLength() +{ + return m_enclosureLength; +} + +void FvAvailableUpdate::SetEnclosureLength(unsigned long enclosureLength) +{ + m_enclosureLength = enclosureLength; +} + +QString FvAvailableUpdate::GetEnclosureType() +{ + return m_enclosureType; +} + +void FvAvailableUpdate::SetEnclosureType(QString enclosureType) +{ + m_enclosureType = enclosureType; +} diff --git a/interface/external/fervor/fvavailableupdate.h b/interface/external/fervor/fvavailableupdate.h new file mode 100755 index 0000000000..6bf3cf9c97 --- /dev/null +++ b/interface/external/fervor/fvavailableupdate.h @@ -0,0 +1,51 @@ +#ifndef FVAVAILABLEUPDATE_H +#define FVAVAILABLEUPDATE_H + +#include +#include + +class FvAvailableUpdate : public QObject +{ + Q_OBJECT +public: + explicit FvAvailableUpdate(QObject *parent = 0); + + QString GetTitle(); + void SetTitle(QString title); + + QUrl GetReleaseNotesLink(); + void SetReleaseNotesLink(QUrl releaseNotesLink); + void SetReleaseNotesLink(QString releaseNotesLink); + + QString GetPubDate(); + void SetPubDate(QString pubDate); + + QUrl GetEnclosureUrl(); + void SetEnclosureUrl(QUrl enclosureUrl); + void SetEnclosureUrl(QString enclosureUrl); + + QString GetEnclosureVersion(); + void SetEnclosureVersion(QString enclosureVersion); + + QString GetEnclosurePlatform(); + void SetEnclosurePlatform(QString enclosurePlatform); + + unsigned long GetEnclosureLength(); + void SetEnclosureLength(unsigned long enclosureLength); + + QString GetEnclosureType(); + void SetEnclosureType(QString enclosureType); + +private: + QString m_title; + QUrl m_releaseNotesLink; + QString m_pubDate; + QUrl m_enclosureUrl; + QString m_enclosureVersion; + QString m_enclosurePlatform; + unsigned long m_enclosureLength; + QString m_enclosureType; + +}; + +#endif // FVAVAILABLEUPDATE_H diff --git a/interface/external/fervor/fvignoredversions.cpp b/interface/external/fervor/fvignoredversions.cpp new file mode 100755 index 0000000000..c665f9cff7 --- /dev/null +++ b/interface/external/fervor/fvignoredversions.cpp @@ -0,0 +1,74 @@ +#include "fvignoredversions.h" +#include "fvversioncomparator.h" +#include +#include +#include + +// QSettings key for the latest skipped version +#define FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY "FVLatestSkippedVersion" + + +FVIgnoredVersions::FVIgnoredVersions(QObject *parent) : +QObject(parent) +{ + // noop +} + +bool FVIgnoredVersions::VersionIsIgnored(QString version) +{ + // We assume that variable 'version' contains either: + // 1) The current version of the application (ignore) + // 2) The version that was skipped before and thus stored in QSettings (ignore) + // 3) A newer version (don't ignore) + // 'version' is not likely to contain an older version in any case. + + if (version == QCoreApplication::applicationVersion()) { + return true; + } + + QSettings settings(QSettings::NativeFormat, + QSettings::UserScope, + QCoreApplication::organizationDomain(), + QCoreApplication::applicationName()); + + //QSettings settings; + if (settings.contains(FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY)) { + QString lastSkippedVersion = settings.value(FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY).toString(); + if (version == lastSkippedVersion) { + // Implicitly skipped version - skip + return true; + } + } + + std::string currentAppVersion = QCoreApplication::applicationVersion().toStdString(); + std::string suggestedVersion = std::string(version.toStdString()); + if (FvVersionComparator::CompareVersions(currentAppVersion, suggestedVersion) == FvVersionComparator::kAscending) { + // Newer version - do not skip + return false; + } + + // Fallback - skip + return true; +} + +void FVIgnoredVersions::IgnoreVersion(QString version) +{ + if (version == QCoreApplication::applicationVersion()) { + // Don't ignore the current version + return; + } + + if (version.isEmpty()) { + return; + } + + QSettings settings(QSettings::NativeFormat, + QSettings::UserScope, + QCoreApplication::organizationDomain(), + QCoreApplication::applicationName()); + + + settings.setValue(FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY, version); + + return; +} \ No newline at end of file diff --git a/interface/external/fervor/fvignoredversions.h b/interface/external/fervor/fvignoredversions.h new file mode 100755 index 0000000000..4e85f1b565 --- /dev/null +++ b/interface/external/fervor/fvignoredversions.h @@ -0,0 +1,19 @@ +#ifndef FVIGNOREDVERSIONS_H +#define FVIGNOREDVERSIONS_H + +#include + +class FVIgnoredVersions : public QObject +{ + Q_OBJECT + +public: + static bool VersionIsIgnored(QString version); + static void IgnoreVersion(QString version); + +private: + explicit FVIgnoredVersions(QObject *parent = 0); + +}; + +#endif // FVIGNOREDVERSIONS_H diff --git a/interface/external/fervor/fvplatform.cpp b/interface/external/fervor/fvplatform.cpp new file mode 100755 index 0000000000..1a90ab1dca --- /dev/null +++ b/interface/external/fervor/fvplatform.cpp @@ -0,0 +1,210 @@ +#include "fvplatform.h" +#include +#include + +FvPlatform::FvPlatform(QObject *parent) : + QObject(parent) +{ + // noop +} + +bool FvPlatform::CurrentlyRunningOnPlatform(QString platform) +{ + platform = platform.toUpper().trimmed(); + if (platform.isEmpty()) { + return false; + } + + // Defined on AIX. +#ifdef Q_OS_AIX + if (platform == "Q_OS_AIX") { + return true; + } +#endif + + // Q_OS_BSD4 ("Defined on Any BSD 4.4 system") intentionally skipped. + + // Defined on BSD/OS. +#ifdef Q_OS_BSDI + if (platform == "Q_OS_BSDI") { + return true; + } +#endif + + // Defined on Cygwin. +#ifdef Q_OS_CYGWIN + if (platform == "Q_OS_CYGWIN") { + return true; + } +#endif + + // Q_OS_DARWIN ("Defined on Darwin OS (synonym for Q_OS_MAC)") intentionally skipped. + + // Defined on DG/UX. +#ifdef Q_OS_DGUX + if (platform == "Q_OS_DGUX") { + return true; + } +#endif + + // Defined on DYNIX/ptx. +#ifdef Q_OS_DYNIX + if (platform == "Q_OS_DYNIX") { + return true; + } +#endif + + // Defined on FreeBSD. +#ifdef Q_OS_FREEBSD + if (platform == "Q_OS_FREEBSD") { + return true; + } +#endif + + // Defined on HP-UX. +#ifdef Q_OS_HPUX + if (platform == "Q_OS_HPUX") { + return true; + } +#endif + + // Defined on GNU Hurd. +#ifdef Q_OS_HURD + if (platform == "Q_OS_HURD") { + return true; + } +#endif + + // Defined on SGI Irix. +#ifdef Q_OS_IRIX + if (platform == "Q_OS_IRIX") { + return true; + } +#endif + + // Defined on Linux. +#ifdef Q_OS_LINUX + if (platform == "Q_OS_LINUX") { + return true; + } +#endif + + // Defined on LynxOS. +#ifdef Q_OS_LYNX + if (platform == "Q_OS_LYNX") { + return true; + } +#endif + + // Defined on MAC OS (synonym for Darwin). +#ifdef Q_OS_MAC + if (platform == "Q_OS_MAC") { + return true; + } +#endif + + // Q_OS_MSDOS ("Defined on MS-DOS and Windows") intentionally skipped. + + // Defined on NetBSD. +#ifdef Q_OS_NETBSD + if (platform == "Q_OS_NETBSD") { + return true; + } +#endif + + // Defined on OS/2. +#ifdef Q_OS_OS2 + if (platform == "Q_OS_OS2") { + return true; + } +#endif + + // Defined on OpenBSD. +#ifdef Q_OS_OPENBSD + if (platform == "Q_OS_OPENBSD") { + return true; + } +#endif + + // Defined on XFree86 on OS/2 (not PM). +#ifdef Q_OS_OS2EMX + if (platform == "Q_OS_OS2EMX") { + return true; + } +#endif + + // Defined on HP Tru64 UNIX. +#ifdef Q_OS_OSF + if (platform == "Q_OS_OSF") { + return true; + } +#endif + + // Defined on QNX Neutrino. +#ifdef Q_OS_QNX + if (platform == "Q_OS_QNX") { + return true; + } +#endif + + // Defined on Reliant UNIX. +#ifdef Q_OS_RELIANT + if (platform == "Q_OS_RELIANT") { + return true; + } +#endif + + // Defined on SCO OpenServer 5. +#ifdef Q_OS_SCO + if (platform == "Q_OS_SCO") { + return true; + } +#endif + + // Defined on Sun Solaris. +#ifdef Q_OS_SOLARIS + if (platform == "Q_OS_SOLARIS") { + return true; + } +#endif + + // Defined on Symbian. +#ifdef Q_OS_SYMBIAN + if (platform == "Q_OS_SYMBIAN") { + return true; + } +#endif + + // Defined on DEC Ultrix. +#ifdef Q_OS_ULTRIX + if (platform == "Q_OS_ULTRIX") { + return true; + } +#endif + + // Q_OS_UNIX ("Defined on Any UNIX BSD/SYSV system") intentionally skipped. + + // Defined on UnixWare 7, Open UNIX 8. +#ifdef Q_OS_UNIXWARE + if (platform == "Q_OS_UNIXWARE") { + return true; + } +#endif + + // Defined on Windows CE (note: goes before Q_OS_WIN32) +#ifdef Q_OS_WINCE + if (platform == "Q_OS_WINCE") { + return true; + } +#endif + + // Defined on all supported versions of Windows. +#ifdef Q_OS_WIN32 + if (platform == "Q_OS_WIN32") { + return true; + } +#endif + + // Fallback + return false; +} diff --git a/interface/external/fervor/fvplatform.h b/interface/external/fervor/fvplatform.h new file mode 100755 index 0000000000..a527518097 --- /dev/null +++ b/interface/external/fervor/fvplatform.h @@ -0,0 +1,18 @@ +#ifndef FVPLATFORM_H +#define FVPLATFORM_H + +#include + +class FvPlatform : public QObject +{ + Q_OBJECT + +public: + static bool CurrentlyRunningOnPlatform(QString platform); + +private: + explicit FvPlatform(QObject *parent = 0); + +}; + +#endif // FVPLATFORM_H diff --git a/interface/external/fervor/fvupdatedownloadprogress.cpp b/interface/external/fervor/fvupdatedownloadprogress.cpp new file mode 100755 index 0000000000..0b6e7934ad --- /dev/null +++ b/interface/external/fervor/fvupdatedownloadprogress.cpp @@ -0,0 +1,26 @@ +#include "fvupdatedownloadprogress.h" + +FvUpdateDownloadProgress::FvUpdateDownloadProgress(QWidget *parent) + : QWidget(parent, Qt::SplashScreen) +{ + ui.setupUi(this); + + ui.progress->setValue(0); + +} + +FvUpdateDownloadProgress::~FvUpdateDownloadProgress() +{ + +} + +void FvUpdateDownloadProgress::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal ) +{ + ui.progress->setValue( ((float)bytesReceived / (float)bytesTotal) * 100 ); +} + +void FvUpdateDownloadProgress::close() +{ + this->deleteLater(); + QWidget::close(); +} \ No newline at end of file diff --git a/interface/external/fervor/fvupdatedownloadprogress.h b/interface/external/fervor/fvupdatedownloadprogress.h new file mode 100755 index 0000000000..1b6ab0d709 --- /dev/null +++ b/interface/external/fervor/fvupdatedownloadprogress.h @@ -0,0 +1,23 @@ +#ifndef FVUPDATEDOWNLOADPROGRESS_H +#define FVUPDATEDOWNLOADPROGRESS_H + +#include +#include "ui_fvupdatedownloadprogress.h" + +class FvUpdateDownloadProgress : public QWidget +{ + Q_OBJECT + +public: + FvUpdateDownloadProgress(QWidget *parent = 0); + ~FvUpdateDownloadProgress(); + +public slots: + void downloadProgress ( qint64 bytesReceived, qint64 bytesTotal ); + void close(); + +private: + Ui::FvUpdateDownloadProgress ui; +}; + +#endif // FVUPDATEDOWNLOADPROGRESS_H diff --git a/interface/external/fervor/fvupdatedownloadprogress.ui b/interface/external/fervor/fvupdatedownloadprogress.ui new file mode 100755 index 0000000000..264e8c9728 --- /dev/null +++ b/interface/external/fervor/fvupdatedownloadprogress.ui @@ -0,0 +1,94 @@ + + + FvUpdateDownloadProgress + + + Qt::ApplicationModal + + + + 0 + 0 + 558 + 70 + + + + + 0 + 0 + + + + FvUpdateDownloadProgress + + + + 0 + + + 0 + + + + + QFrame::WinPanel + + + QFrame::Raised + + + + 8 + + + + + + 12 + + + + Qt::LeftToRight + + + QFrame::NoFrame + + + QFrame::Plain + + + Downloading Update... + + + Qt::AlignCenter + + + + + + + + 8 + + + + 24 + + + false + + + false + + + + + + + + + + + + diff --git a/interface/external/fervor/fvupdater.cpp b/interface/external/fervor/fvupdater.cpp new file mode 100755 index 0000000000..66bdc29163 --- /dev/null +++ b/interface/external/fervor/fvupdater.cpp @@ -0,0 +1,908 @@ +#include "fvupdater.h" +#include "fvplatform.h" +#include "fvignoredversions.h" +#include "fvavailableupdate.h" +#include +#include +#include +#include +#include "quazip.h" +#include "quazipfile.h" + +#ifdef Q_WS_MAC +#include "CoreFoundation/CoreFoundation.h" +#endif + +#ifdef FV_GUI +#include "fvupdatewindow.h" +#include "fvupdatedownloadprogress.h" +#include +#include +#else +// QSettings key for automatic update installation +#define FV_NEW_VERSION_POLICY_KEY "FVNewVersionPolicy" +#endif + +#ifdef FV_DEBUG + // Unit tests +# include "fvversioncomparatortest.h" +#endif + +extern QSettings* settings; + +FvUpdater* FvUpdater::m_Instance = 0; + + +FvUpdater* FvUpdater::sharedUpdater() +{ + static QMutex mutex; + if (! m_Instance) { + mutex.lock(); + + if (! m_Instance) { + m_Instance = new FvUpdater; + } + + mutex.unlock(); + } + + return m_Instance; +} + +void FvUpdater::drop() +{ + static QMutex mutex; + mutex.lock(); + delete m_Instance; + m_Instance = 0; + mutex.unlock(); +} + +FvUpdater::FvUpdater() : QObject(0) +{ + m_reply = 0; +#ifdef FV_GUI + m_updaterWindow = 0; +#endif + m_proposedUpdate = 0; + m_requiredSslFingerprint = ""; + htAuthUsername = ""; + htAuthPassword = ""; + skipVersionAllowed = true; + remindLaterAllowed = true; + + connect(&m_qnam, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),this, SLOT(authenticationRequired(QNetworkReply*, QAuthenticator*))); + + // Translation mechanism + installTranslator(); + +#ifdef FV_DEBUG + // Unit tests + FvVersionComparatorTest* test = new FvVersionComparatorTest(); + test->runAll(); + delete test; +#endif + +} + +FvUpdater::~FvUpdater() +{ + if (m_proposedUpdate) { + delete m_proposedUpdate; + m_proposedUpdate = 0; + } + +#ifdef FV_GUI + hideUpdaterWindow(); +#endif +} + +void FvUpdater::installTranslator() +{ + QTranslator translator; + QString locale = QLocale::system().name(); + translator.load(QString("fervor_") + locale); + +#if QT_VERSION < 0x050000 + QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8")); +#endif + + qApp->installTranslator(&translator); +} + +#ifdef FV_GUI +void FvUpdater::showUpdaterWindowUpdatedWithCurrentUpdateProposal() +{ + // Destroy window if already exists + hideUpdaterWindow(); + + // Create a new window + m_updaterWindow = new FvUpdateWindow(NULL, skipVersionAllowed, remindLaterAllowed); + m_updaterWindow->UpdateWindowWithCurrentProposedUpdate(); + m_updaterWindow->show(); +} + +void FvUpdater::hideUpdaterWindow() +{ + if (m_updaterWindow) { + if (! m_updaterWindow->close()) { + qWarning() << "Update window didn't close, leaking memory from now on"; + } + + // not deleting because of Qt::WA_DeleteOnClose + + m_updaterWindow = 0; + } +} + +void FvUpdater::updaterWindowWasClosed() +{ + // (Re-)nullify a pointer to a destroyed QWidget or you're going to have a bad time. + m_updaterWindow = 0; +} +#endif + +void FvUpdater::SetFeedURL(QUrl feedURL) +{ + m_feedURL = feedURL; +} + +void FvUpdater::SetFeedURL(QString feedURL) +{ + SetFeedURL(QUrl(feedURL)); +} + +QString FvUpdater::GetFeedURL() +{ + return m_feedURL.toString(); +} + +FvAvailableUpdate* FvUpdater::GetProposedUpdate() +{ + return m_proposedUpdate; +} + + +void FvUpdater::InstallUpdate() +{ + if(m_proposedUpdate==NULL) + { + qWarning() << "Abort Update: No update prososed! This should not happen."; + return; + } + + // Prepare download + QUrl url = m_proposedUpdate->GetEnclosureUrl(); + + // Check SSL Fingerprint if required + if(url.scheme()=="https" && !m_requiredSslFingerprint.isEmpty()) + if( !checkSslFingerPrint(url) ) // check failed + { + qWarning() << "Update aborted."; + return; + } + + // Start Download + QNetworkReply* reply = m_qnam.get(QNetworkRequest(url)); + connect(reply, SIGNAL(finished()), this, SLOT(httpUpdateDownloadFinished())); + + // Maybe Check request 's return value + if (reply->error() != QNetworkReply::NoError) + { + qDebug()<<"Unable to download the update: "<errorString(); + return; + } + else + qDebug()<<"OK"; + + // Show download Window +#ifdef FV_GUI + FvUpdateDownloadProgress* dlwindow = new FvUpdateDownloadProgress(NULL); + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), dlwindow, SLOT(downloadProgress(qint64, qint64) )); + connect(&m_qnam, SIGNAL(finished(QNetworkReply*)), dlwindow, SLOT(close())); + dlwindow->show(); +#endif + + emit (updatedFinishedSuccessfully()); + +#ifdef FV_GUI + hideUpdaterWindow(); +#endif +} + +void FvUpdater::httpUpdateDownloadFinished() +{ + QNetworkReply* reply = qobject_cast(sender()); + if(reply==NULL) + { + qWarning()<<"The slot httpUpdateDownloadFinished() should only be invoked by S&S."; + return; + } + + if(reply->error() == QNetworkReply::NoError) + { + int httpstatuscode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); + + // no error received? + if (reply->error() == QNetworkReply::NoError) + { + if (reply->isReadable()) + { +#ifdef Q_WS_MAC + CFURLRef appURLRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + char path[PATH_MAX]; + if (!CFURLGetFileSystemRepresentation(appURLRef, TRUE, (UInt8 *)path, PATH_MAX)) { + // error! + } + + CFRelease(appURLRef); + QString filePath = QString(path); + QString rootDirectory = filePath.left(filePath.lastIndexOf("/")); +#else + QString rootDirectory = QCoreApplication::applicationDirPath() + "/"; +#endif + + // Write download into File + QFileInfo fileInfo=reply->url().path(); + QString fileName = rootDirectory + fileInfo.fileName(); + //qDebug()<<"Writing downloaded file into "<readAll()); + file.close(); + + // Retrieve List of updated files (Placed in an extra scope to avoid QuaZIP handles the archive permanently and thus avoids the deletion.) + { + QuaZip zip(fileName); + if (!zip.open(QuaZip::mdUnzip)) { + qWarning("testRead(): zip.open(): %d", zip.getZipError()); + return; + } + zip.setFileNameCodec("IBM866"); + QList updateFiles = zip.getFileInfoList(); + + // Rename all current files with available update. + for (int i=0;ideleteLater(); + } // If !reply->error END +} // httpUpdateDownloadFinished END + +bool FvUpdater::unzipUpdate(const QString & filePath, const QString & extDirPath, const QString & singleFileName ) +{ + QuaZip zip(filePath); + + if (!zip.open(QuaZip::mdUnzip)) { + qWarning()<GetEnclosureVersion()); + +#ifdef FV_GUI + hideUpdaterWindow(); +#endif +} + +void FvUpdater::RemindMeLater() +{ + //qDebug() << "Remind me later"; + +#ifdef FV_GUI + hideUpdaterWindow(); +#endif +} + +bool FvUpdater::CheckForUpdates(bool silentAsMuchAsItCouldGet) +{ + if (m_feedURL.isEmpty()) { + qCritical() << "Please set feed URL via setFeedURL() before calling CheckForUpdates()."; + return false; + } + + m_silentAsMuchAsItCouldGet = silentAsMuchAsItCouldGet; + + // Check if application's organization name and domain are set, fail otherwise + // (nowhere to store QSettings to) + if (QCoreApplication::organizationName().isEmpty()) { + qCritical() << "QCoreApplication::organizationName is not set. Please do that."; + return false; + } + if (QCoreApplication::organizationDomain().isEmpty()) { + qCritical() << "QCoreApplication::organizationDomain is not set. Please do that."; + return false; + } + + if(QCoreApplication::applicationName().isEmpty()) { + qCritical() << "QCoreApplication::applicationName is not set. Please do that."; + return false; + } + + // Set application version is not set yet + if (QCoreApplication::applicationVersion().isEmpty()) { + qCritical() << "QCoreApplication::applicationVersion is not set. Please do that."; + return false; + } + + cancelDownloadFeed(); + m_httpRequestAborted = false; + startDownloadFeed(m_feedURL); + + return true; +} + +bool FvUpdater::CheckForUpdatesSilent() +{ + return CheckForUpdates(true); +} + +bool FvUpdater::CheckForUpdatesNotSilent() +{ + return CheckForUpdates(false); +} + + +void FvUpdater::startDownloadFeed(QUrl url) +{ + m_xml.clear(); + + // Check SSL Fingerprint if required + if(url.scheme()=="https" && !m_requiredSslFingerprint.isEmpty()) + if( !checkSslFingerPrint(url) ) // check failed + { + qWarning() << "Update aborted."; + return; + } + + + m_reply = m_qnam.get(QNetworkRequest(url)); + + connect(m_reply, SIGNAL(readyRead()), this, SLOT(httpFeedReadyRead())); + connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(httpFeedUpdateDataReadProgress(qint64, qint64))); + connect(m_reply, SIGNAL(finished()), this, SLOT(httpFeedDownloadFinished())); +} + +void FvUpdater::cancelDownloadFeed() +{ + if (m_reply) { + m_httpRequestAborted = true; + m_reply->abort(); + } +} + +void FvUpdater::httpFeedReadyRead() +{ + // this slot gets called every time the QNetworkReply has new data. + // We read all of its new data and write it into the file. + // That way we use less RAM than when reading it at the finished() + // signal of the QNetworkReply + m_xml.addData(m_reply->readAll()); +} + +void FvUpdater::httpFeedUpdateDataReadProgress(qint64 bytesRead, + qint64 totalBytes) +{ + Q_UNUSED(bytesRead); + Q_UNUSED(totalBytes); + + if (m_httpRequestAborted) { + return; + } +} + +void FvUpdater::httpFeedDownloadFinished() +{ + if (m_httpRequestAborted) { + m_reply->deleteLater(); + return; + } + + QVariant redirectionTarget = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (m_reply->error()) { + + // Error. + showErrorDialog(tr("Feed download failed: %1.").arg(m_reply->errorString()), false); + + } else if (! redirectionTarget.isNull()) { + QUrl newUrl = m_feedURL.resolved(redirectionTarget.toUrl()); + + m_feedURL = newUrl; + m_reply->deleteLater(); + + startDownloadFeed(m_feedURL); + return; + + } else { + + // Done. + xmlParseFeed(); + + } + + m_reply->deleteLater(); + m_reply = 0; +} + +bool FvUpdater::xmlParseFeed() +{ + QString currentTag, currentQualifiedTag; + + QString xmlTitle, xmlLink, xmlReleaseNotesLink, xmlPubDate, xmlEnclosureUrl, + xmlEnclosureVersion, xmlEnclosurePlatform, xmlEnclosureType; + unsigned long xmlEnclosureLength; + + // Parse + while (! m_xml.atEnd()) { + + m_xml.readNext(); + + if (m_xml.isStartElement()) { + + currentTag = m_xml.name().toString(); + currentQualifiedTag = m_xml.qualifiedName().toString(); + + if (m_xml.name() == "item") { + + xmlTitle.clear(); + xmlLink.clear(); + xmlReleaseNotesLink.clear(); + xmlPubDate.clear(); + xmlEnclosureUrl.clear(); + xmlEnclosureVersion.clear(); + xmlEnclosurePlatform.clear(); + xmlEnclosureLength = 0; + xmlEnclosureType.clear(); + + } else if (m_xml.name() == "enclosure") { + + QXmlStreamAttributes attribs = m_xml.attributes(); + + if (attribs.hasAttribute("fervor:platform")) + { + xmlEnclosurePlatform = attribs.value("fervor:platform").toString().trimmed(); + + if (FvPlatform::CurrentlyRunningOnPlatform(xmlEnclosurePlatform)) + { + xmlEnclosureUrl = attribs.hasAttribute("url") ? attribs.value("url").toString().trimmed() : ""; + + xmlEnclosureVersion = ""; + if (attribs.hasAttribute("fervor:version")) + xmlEnclosureVersion = attribs.value("fervor:version").toString().trimmed(); + if (attribs.hasAttribute("sparkle:version")) + xmlEnclosureVersion = attribs.value("sparkle:version").toString().trimmed(); + + xmlEnclosureLength = attribs.hasAttribute("length") ? attribs.value("length").toString().toLong() : 0; + + xmlEnclosureType = attribs.hasAttribute("type") ? attribs.value("type").toString().trimmed() : ""; + } + + } // if hasAttribute flevor:platform END + + } // IF encosure END + + } else if (m_xml.isEndElement()) { + + if (m_xml.name() == "item") { + + // That's it - we have analyzed a single and we'll stop + // here (because the topmost is the most recent one, and thus + // the newest version. + + return searchDownloadedFeedForUpdates(xmlTitle, + xmlLink, + xmlReleaseNotesLink, + xmlPubDate, + xmlEnclosureUrl, + xmlEnclosureVersion, + xmlEnclosurePlatform, + xmlEnclosureLength, + xmlEnclosureType); + + } + + } else if (m_xml.isCharacters() && ! m_xml.isWhitespace()) { + + if (currentTag == "title") { + xmlTitle += m_xml.text().toString().trimmed(); + + } else if (currentTag == "link") { + xmlLink += m_xml.text().toString().trimmed(); + + } else if (currentQualifiedTag == "sparkle:releaseNotesLink") { + xmlReleaseNotesLink += m_xml.text().toString().trimmed(); + + } else if (currentTag == "pubDate") { + xmlPubDate += m_xml.text().toString().trimmed(); + + } + + } + + if (m_xml.error() && m_xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) { + + showErrorDialog(tr("Feed parsing failed: %1 %2.").arg(QString::number(m_xml.lineNumber()), m_xml.errorString()), false); + return false; + + } + } + + return false; +} + + +bool FvUpdater::searchDownloadedFeedForUpdates(QString xmlTitle, + QString xmlLink, + QString xmlReleaseNotesLink, + QString xmlPubDate, + QString xmlEnclosureUrl, + QString xmlEnclosureVersion, + QString xmlEnclosurePlatform, + unsigned long xmlEnclosureLength, + QString xmlEnclosureType) +{ + Q_UNUSED(xmlTitle); + Q_UNUSED(xmlPubDate); + Q_UNUSED(xmlEnclosureLength); + Q_UNUSED(xmlEnclosureType); + + // Validate + if (xmlReleaseNotesLink.isEmpty()) { + if (xmlLink.isEmpty()) { + showErrorDialog(tr("Feed error: \"release notes\" link is empty"), false); + return false; + } else { + xmlReleaseNotesLink = xmlLink; + } + } else { + xmlLink = xmlReleaseNotesLink; + } + if (! (xmlLink.startsWith("http://") || xmlLink.startsWith("https://"))) { + showErrorDialog(tr("Feed error: invalid \"release notes\" link"), false); + return false; + } + if (xmlEnclosureUrl.isEmpty() || xmlEnclosureVersion.isEmpty() || xmlEnclosurePlatform.isEmpty()) { + showErrorDialog(tr("Feed error: invalid \"enclosure\" with the download link"), false); + return false; + } + + // Relevant version? + if (FVIgnoredVersions::VersionIsIgnored(xmlEnclosureVersion)) { + qDebug() << "Version '" << xmlEnclosureVersion << "' is ignored, too old or something like that."; + + showInformationDialog(tr("No updates were found."), false); + + return true; // Things have succeeded when you think of it. + } + + + // + // Success! At this point, we have found an update that can be proposed + // to the user. + // + + if (m_proposedUpdate) { + delete m_proposedUpdate; m_proposedUpdate = 0; + } + m_proposedUpdate = new FvAvailableUpdate(); + m_proposedUpdate->SetTitle(xmlTitle); + m_proposedUpdate->SetReleaseNotesLink(xmlReleaseNotesLink); + m_proposedUpdate->SetPubDate(xmlPubDate); + m_proposedUpdate->SetEnclosureUrl(xmlEnclosureUrl); + m_proposedUpdate->SetEnclosureVersion(xmlEnclosureVersion); + m_proposedUpdate->SetEnclosurePlatform(xmlEnclosurePlatform); + m_proposedUpdate->SetEnclosureLength(xmlEnclosureLength); + m_proposedUpdate->SetEnclosureType(xmlEnclosureType); + +#ifdef FV_GUI + // Show "look, there's an update" window + showUpdaterWindowUpdatedWithCurrentUpdateProposal(); +#else + // Decide ourselves what to do + decideWhatToDoWithCurrentUpdateProposal(); +#endif + + return true; +} + + +void FvUpdater::showErrorDialog(QString message, bool showEvenInSilentMode) +{ + if (m_silentAsMuchAsItCouldGet) { + if (! showEvenInSilentMode) { + // Don't show errors in the silent mode + return; + } + } + +#ifdef FV_GUI + QMessageBox dlFailedMsgBox; + dlFailedMsgBox.setIcon(QMessageBox::Critical); + dlFailedMsgBox.setText(tr("Error")); + dlFailedMsgBox.setInformativeText(message); + dlFailedMsgBox.exec(); +#else + qCritical() << message; +#endif +} + +void FvUpdater::showInformationDialog(QString message, bool showEvenInSilentMode) +{ + if (m_silentAsMuchAsItCouldGet) { + if (! showEvenInSilentMode) { + // Don't show information dialogs in the silent mode + return; + } + } + +#ifdef FV_GUI + QMessageBox dlInformationMsgBox; + dlInformationMsgBox.setIcon(QMessageBox::Information); + dlInformationMsgBox.setText(tr("Information")); + dlInformationMsgBox.setInformativeText(message); + dlInformationMsgBox.exec(); +#else + qDebug() << message; +#endif +} + +void FvUpdater::finishUpdate(QString pathToFinish) +{ + pathToFinish = pathToFinish.isEmpty() ? QCoreApplication::applicationDirPath() : pathToFinish; + QDir appDir(pathToFinish); + appDir.setFilter( QDir::Files | QDir::Dirs ); + + QFileInfoList dirEntries = appDir.entryInfoList(); + foreach (QFileInfo fi, dirEntries) + { + if ( fi.isDir() ) + { + QString dirname = fi.fileName(); + if ((dirname==".") || (dirname == "..")) + continue; + + // recursive clean up subdirectory + finishUpdate(fi.filePath()); + } + else + { + if(fi.suffix()=="oldversion") + if( !appDir.remove( fi.absoluteFilePath() ) ) + qDebug()<<"Error: Unable to clean up file: "<connectToHostEncrypted(urltoCheck.host(), 443); + if( !socket->waitForEncrypted(1000)) // waits until ssl emits encrypted(), max 1000msecs + { + qWarning()<<"SSL fingerprint check: Unable to connect SSL server: "<sslErrors(); + return false; + } + + QSslCertificate cert = socket->peerCertificate(); + + if(cert.isNull()) + { + qWarning()<<"SSL fingerprint check: Unable to retrieve SSL server certificate."; + return false; + } + + // COmpare digests + if(cert.digest().toHex() != m_requiredSslFingerprint) + { + qWarning()<<"SSL fingerprint check: FINGERPRINT MISMATCH! Server digest="<setUser(htAuthUsername); + authenticator->setPassword(htAuthPassword); +} + +void FvUpdater::setHtAuthCredentials(QString user, QString pass) +{ + htAuthUsername = user; + htAuthPassword = pass; +} + +void FvUpdater::setHtAuthUsername(QString user) +{ + htAuthUsername = user; +} + +void FvUpdater::setHtAuthPassword(QString pass) +{ + htAuthPassword = pass; +} + +void FvUpdater::setSkipVersionAllowed(bool allowed) +{ + skipVersionAllowed = allowed; +} + +void FvUpdater::setRemindLaterAllowed(bool allowed) +{ + remindLaterAllowed = allowed; +} + +bool FvUpdater::getSkipVersionAllowed() +{ + return skipVersionAllowed; +} + +bool FvUpdater::getRemindLaterAllowed() +{ + return remindLaterAllowed; +} + +#ifndef FV_GUI + +void FvUpdater::decideWhatToDoWithCurrentUpdateProposal() +{ + QString policy = settings->value(FV_NEW_VERSION_POLICY_KEY).toString(); + if(policy == "install") + InstallUpdate(); + else if(policy == "skip") + SkipUpdate(); + else + RemindMeLater(); +} + +#endif + diff --git a/interface/external/fervor/fvupdater.h b/interface/external/fervor/fvupdater.h new file mode 100755 index 0000000000..48baf0c194 --- /dev/null +++ b/interface/external/fervor/fvupdater.h @@ -0,0 +1,183 @@ +#ifndef FVUPDATER_H +#define FVUPDATER_H + +#include +#include +#include +#include +#include +class QNetworkReply; +class FvUpdateWindow; +class FvUpdateConfirmDialog; +class FvAvailableUpdate; + + +class FvUpdater : public QObject +{ + Q_OBJECT + +public: + + // Singleton + static FvUpdater* sharedUpdater(); + static void drop(); + + // Set / get feed URL + void SetFeedURL(QUrl feedURL); + void SetFeedURL(QString feedURL); + QString GetFeedURL(); + void finishUpdate(QString pathToFinish = ""); + void setRequiredSslFingerPrint(QString md5); + QString getRequiredSslFingerPrint(); // returns md5! + // HTTP Authentuication - for security reasons no getters are provided, only a setter + void setHtAuthCredentials(QString user, QString pass); + void setHtAuthUsername(QString user); + void setHtAuthPassword(QString pass); + void setSkipVersionAllowed(bool allowed); + void setRemindLaterAllowed(bool allowed); + bool getSkipVersionAllowed(); + bool getRemindLaterAllowed(); + + +public slots: + + // Check for updates + bool CheckForUpdates(bool silentAsMuchAsItCouldGet = true); + + // Aliases + bool CheckForUpdatesSilent(); + bool CheckForUpdatesNotSilent(); + + + // + // --------------------------------------------------- + // --------------------------------------------------- + // --------------------------------------------------- + // --------------------------------------------------- + // + +protected: + + friend class FvUpdateWindow; // Uses GetProposedUpdate() and others + friend class FvUpdateConfirmDialog; // Uses GetProposedUpdate() and others + FvAvailableUpdate* GetProposedUpdate(); + + +protected slots: + + // Update window button slots + void InstallUpdate(); + void SkipUpdate(); + void RemindMeLater(); + +private: + + // + // Singleton business + // + // (we leave just the declarations, so the compiler will warn us if we try + // to use those two functions by accident) + FvUpdater(); // Hide main constructor + ~FvUpdater(); // Hide main destructor + FvUpdater(const FvUpdater&); // Hide copy constructor + FvUpdater& operator=(const FvUpdater&); // Hide assign op + + static FvUpdater* m_Instance; // Singleton instance + + + // + // Windows / dialogs + // +#ifdef FV_GUI + FvUpdateWindow* m_updaterWindow; // Updater window (NULL if not shown) + void showUpdaterWindowUpdatedWithCurrentUpdateProposal(); // Show updater window + void hideUpdaterWindow(); // Hide + destroy m_updaterWindow + void updaterWindowWasClosed(); // Sent by the updater window when it gets closed +#else + void decideWhatToDoWithCurrentUpdateProposal(); // Perform an action which is configured in settings +#endif + + // Available update (NULL if not fetched) + FvAvailableUpdate* m_proposedUpdate; + + // If true, don't show the error dialogs and the "no updates." dialog + // (silentAsMuchAsItCouldGet from CheckForUpdates() goes here) + // Useful for automatic update checking upon application startup. + bool m_silentAsMuchAsItCouldGet; + + // Dialogs (notifications) + bool skipVersionAllowed; + bool remindLaterAllowed; + + void showErrorDialog(QString message, bool showEvenInSilentMode = false); // Show an error message + void showInformationDialog(QString message, bool showEvenInSilentMode = false); // Show an informational message + + + // + // HTTP feed fetcher infrastructure + // + QUrl m_feedURL; // Feed URL that will be fetched + QNetworkAccessManager m_qnam; + QNetworkReply* m_reply; + int m_httpGetId; + bool m_httpRequestAborted; + + void startDownloadFeed(QUrl url); // Start downloading feed + void cancelDownloadFeed(); // Stop downloading the current feed + + // + // SSL Fingerprint Check infrastructure + // + QString m_requiredSslFingerprint; + + bool checkSslFingerPrint(QUrl urltoCheck); // true=ssl Fingerprint accepted, false= ssl Fingerprint NOT accepted + + // + // Htauth-Infrastructure + // + QString htAuthUsername; + QString htAuthPassword; + + + // + // XML parser + // + QXmlStreamReader m_xml; // XML data collector and parser + bool xmlParseFeed(); // Parse feed in m_xml + bool searchDownloadedFeedForUpdates(QString xmlTitle, + QString xmlLink, + QString xmlReleaseNotesLink, + QString xmlPubDate, + QString xmlEnclosureUrl, + QString xmlEnclosureVersion, + QString xmlEnclosurePlatform, + unsigned long xmlEnclosureLength, + QString xmlEnclosureType); + + + // + // Helpers + // + void installTranslator(); // Initialize translation mechanism + void restartApplication(); // Restarts application after update + +private slots: + + void authenticationRequired ( QNetworkReply * reply, QAuthenticator * authenticator ); + void httpFeedReadyRead(); + void httpFeedUpdateDataReadProgress(qint64 bytesRead, + qint64 totalBytes); + void httpFeedDownloadFinished(); + + // + // Download and install Update infrastructure + // + void httpUpdateDownloadFinished(); + bool unzipUpdate(const QString & filePath, const QString & extDirPath, const QString & singleFileName = QString("")); // returns true on success + +signals: + void updatedFinishedSuccessfully(); + +}; + +#endif // FVUPDATER_H diff --git a/interface/external/fervor/fvupdatewindow.cpp b/interface/external/fervor/fvupdatewindow.cpp new file mode 100755 index 0000000000..75329a54f3 --- /dev/null +++ b/interface/external/fervor/fvupdatewindow.cpp @@ -0,0 +1,66 @@ +#include "fvupdatewindow.h" +#include "ui_fvupdatewindow.h" +#include "fvupdater.h" +#include "fvavailableupdate.h" +#include +#include +#include + + +FvUpdateWindow::FvUpdateWindow(QWidget *parent, bool skipVersionAllowed, bool remindLaterAllowed) : + QWidget(parent, Qt::CustomizeWindowHint), + m_ui(new Ui::FvUpdateWindow) +{ + m_ui->setupUi(this); + + m_appIconScene = 0; + + if(!skipVersionAllowed) + m_ui->skipThisVersionButton->hide(); + if(!remindLaterAllowed) + m_ui->remindMeLaterButton->hide(); + + // Delete on close + setAttribute(Qt::WA_DeleteOnClose, true); + + // Set the "new version is available" string + QString newVersString = m_ui->newVersionIsAvailableLabel->text().arg(QApplication::applicationName()); + m_ui->newVersionIsAvailableLabel->setText(newVersString); + + // Connect buttons + connect(m_ui->installUpdateButton, SIGNAL(clicked()), + FvUpdater::sharedUpdater(), SLOT(InstallUpdate())); + connect(m_ui->skipThisVersionButton, SIGNAL(clicked()), + FvUpdater::sharedUpdater(), SLOT(SkipUpdate())); + connect(m_ui->remindMeLaterButton, SIGNAL(clicked()), + FvUpdater::sharedUpdater(), SLOT(RemindMeLater())); +} + +FvUpdateWindow::~FvUpdateWindow() +{ + m_ui->releaseNotesWebView->stop(); + delete m_ui; +} + +bool FvUpdateWindow::UpdateWindowWithCurrentProposedUpdate() +{ + FvAvailableUpdate* proposedUpdate = FvUpdater::sharedUpdater()->GetProposedUpdate(); + if (! proposedUpdate) { + return false; + } + + QString downloadString = m_ui->wouldYouLikeToDownloadLabel->text() + .arg(QApplication::applicationName(), proposedUpdate->GetEnclosureVersion(), QApplication::applicationVersion()); + m_ui->wouldYouLikeToDownloadLabel->setText(downloadString); + + m_ui->releaseNotesWebView->stop(); + m_ui->releaseNotesWebView->load(proposedUpdate->GetReleaseNotesLink()); + + return true; +} + +void FvUpdateWindow::closeEvent(QCloseEvent* event) +{ + FvUpdater::sharedUpdater()->updaterWindowWasClosed(); + event->accept(); +} diff --git a/interface/external/fervor/fvupdatewindow.h b/interface/external/fervor/fvupdatewindow.h new file mode 100755 index 0000000000..b2e3feb443 --- /dev/null +++ b/interface/external/fervor/fvupdatewindow.h @@ -0,0 +1,37 @@ +#ifndef FVUPDATEWINDOW_H +#define FVUPDATEWINDOW_H + +#if QT_VERSION >= 0x050000 + #include +#else + #include +#endif + +class QGraphicsScene; + +namespace Ui { +class FvUpdateWindow; +} + +class FvUpdateWindow : public QWidget +{ + Q_OBJECT + +public: + explicit FvUpdateWindow(QWidget *parent, bool skipVersionAllowed, bool remindLaterAllowed); + ~FvUpdateWindow(); + + // Update the current update proposal from FvUpdater + bool UpdateWindowWithCurrentProposedUpdate(); + + void closeEvent(QCloseEvent* event); + +private: + Ui::FvUpdateWindow* m_ui; + QGraphicsScene* m_appIconScene; + +}; + +#endif // FVUPDATEWINDOW_H + + diff --git a/interface/external/fervor/fvupdatewindow.ui b/interface/external/fervor/fvupdatewindow.ui new file mode 100755 index 0000000000..c59646bde7 --- /dev/null +++ b/interface/external/fervor/fvupdatewindow.ui @@ -0,0 +1,129 @@ + + + FvUpdateWindow + + + + 0 + 0 + 640 + 480 + + + + Software Update + + + + QLayout::SetFixedSize + + + + + + + + 75 + true + + + + A new version of %1 is available! + + + + + + + %1 %2 is now available - you have %3. Would you like to download it now? + + + + + + + + 75 + true + + + + Release Notes: + + + + + + + 160 + 80 + + + + + about:blank + + + + + + + + + + + + + Skip This Version + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Remind Me Later + + + + + + + Install Update + + + true + + + true + + + + + + + + + + + + QWebView + QWidget +
QtWebKit/QWebView
+
+
+ + +
diff --git a/interface/external/fervor/fvversioncomparator.cpp b/interface/external/fervor/fvversioncomparator.cpp new file mode 100755 index 0000000000..dc4da8a086 --- /dev/null +++ b/interface/external/fervor/fvversioncomparator.cpp @@ -0,0 +1,164 @@ +#include "fvversioncomparator.h" +#include +#include +#include + +// +// Clone of Sparkle's SUStandardVersionComparator.m, so here's original author's +// copyright too: +// +// Copyright 2007 Andy Matuschak. All rights reserved. +// +// Everything's the same except for TypeOfCharacter() +// (because who knows how Foundation does isdigit() and such.) +// + + +FvVersionComparator::FvVersionComparator() +{ + // noop +} + +FvVersionComparator::CharacterType FvVersionComparator::TypeOfCharacter(std::string character) +{ + if (character == ".") { + return kSeparatorType; + } else if (isdigit(character[0])) { + return kNumberType; + } else if (isspace(character[0])) { + return kSeparatorType; + } else if (ispunct(character[0])) { + return kSeparatorType; + } else { + return kStringType; + } + +} + +std::vector FvVersionComparator::SplitVersionString(std::string version) +{ + std::string character; + std::string s; + unsigned long i = 0, n = 0; + CharacterType oldType, newType; + std::vector parts; + + if (version.length() == 0) { + // Nothing to do here + return parts; + } + + s = version.substr(0, 1); + oldType = TypeOfCharacter(s); + n = version.length() - 1; + for (i = 1; i <= n; ++i) { + character = version.substr(i, 1)[0]; + newType = TypeOfCharacter(character); + if (oldType != newType || oldType == kSeparatorType) { + // We've reached a new segment + std::string aPart = s; + parts.push_back(aPart); + s = character; + } else { + // Add character to string and continue + s.append(character); + } + oldType = newType; + } + + // Add the last part onto the array + parts.push_back(s); + return parts; +} + + +FvVersionComparator::ComparatorResult FvVersionComparator::CompareVersions(std::string versionA, + std::string versionB) +{ + std::vector partsA = SplitVersionString(versionA); + std::vector partsB = SplitVersionString(versionB); + + std::string partA = std::string(""), partB = std::string(""); + unsigned long i = 0, n = 0; + int intA, intB; + CharacterType typeA, typeB; + + n = std::min(partsA.size(), partsB.size()); + for (i = 0; i < n; ++i) { + partA = partsA.at(i); + partB = partsB.at(i); + + typeA = TypeOfCharacter(partA); + typeB = TypeOfCharacter(partB); + + // Compare types + if (typeA == typeB) { + // Same type; we can compare + if (typeA == kNumberType) { + intA = atoi(partA.c_str()); + intB = atoi(partB.c_str()); + + if (intA > intB) { + return kDescending; + } else if (intA < intB) { + return kAscending; + } + } else if (typeA == kStringType) { + short result = partA.compare(partB); + switch (result) { + case -1: return kAscending; break; + case 1: return kDescending; break; + case 0: /* do nothing */ break; + }; + } + } else { + // Not the same type? Now we have to do some validity checking + if (typeA != kStringType && typeB == kStringType) { + // typeA wins + return kDescending; + } else if (typeA == kStringType && typeB != kStringType) { + // typeB wins + return kAscending; + } else { + // One is a number and the other is a period. The period is invalid + if (typeA == kNumberType) { + return kDescending; + } else { + return kAscending; + } + } + } + } + // The versions are equal up to the point where they both still have parts + // Lets check to see if one is larger than the other + if (partsA.size() != partsB.size()) { + // Yep. Lets get the next part of the larger + // n holds the index of the part we want. + std::string missingPart = std::string(""); + CharacterType missingType; + ComparatorResult shorterResult, largerResult; + + if (partsA.size() > partsB.size()) { + missingPart = partsA.at(n); + shorterResult = kAscending; + largerResult = kDescending; + } else { + missingPart = partsB.at(n); + shorterResult = kDescending; + largerResult = kAscending; + } + + missingType = TypeOfCharacter(missingPart); + // Check the type + if (missingType == kStringType) { + // It's a string. Shorter version wins + return shorterResult; + } else { + // It's a number/period. Larger version wins + return largerResult; + } + } + + // The 2 strings are identical + return kSame; +} diff --git a/interface/external/fervor/fvversioncomparator.h b/interface/external/fervor/fvversioncomparator.h new file mode 100755 index 0000000000..f083fdfe03 --- /dev/null +++ b/interface/external/fervor/fvversioncomparator.h @@ -0,0 +1,36 @@ +#ifndef FVVERSIONCOMPARATOR_H +#define FVVERSIONCOMPARATOR_H + +#include +#include + + +class FvVersionComparator +{ +public: + + typedef enum { + kSame = 0, + kDescending = 1, + kAscending = -1 + } ComparatorResult; + + static ComparatorResult CompareVersions(std::string versionA, + std::string versionB); + +private: + + FvVersionComparator(); + + typedef enum { + kNumberType, + kStringType, + kSeparatorType + } CharacterType; + + static CharacterType TypeOfCharacter(std::string character); + static std::vector SplitVersionString(std::string version); + +}; + +#endif // FVVERSIONCOMPARATOR_H diff --git a/interface/resources/info/ApplicationInfo.ini b/interface/resources/info/ApplicationInfo.ini new file mode 100644 index 0000000000..76e481ffce --- /dev/null +++ b/interface/resources/info/ApplicationInfo.ini @@ -0,0 +1,5 @@ +[INFO] +name=interface +version=0.0.1 +organizationName=High Fidelity +organizationDomain=highfidelity.io \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 34aef00950..6d94f361b3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -61,6 +61,7 @@ #include "Util.h" #include "renderer/ProgramObject.h" #include "ui/TextRenderer.h" +#include "fvupdater.h" using namespace std; @@ -246,10 +247,28 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _window->setCentralWidget(_glWidget); - // these are used, for example, to identify the application settings - setApplicationName("Interface"); - setOrganizationDomain("highfidelity.io"); - setOrganizationName("High Fidelity"); +#ifdef Q_WS_MAC + QString resourcesPath = QCoreApplication::applicationDirPath() + "/../Resources"; +#else + QString resourcesPath = QCoreApplication::applicationDirPath() + "/resources"; +#endif + + // read the ApplicationInfo.ini file for Name/Version/Domain information + QSettings applicationInfo(resourcesPath + "/info/ApplicationInfo.ini", QSettings::IniFormat); + + // set the associated application properties + applicationInfo.beginGroup("INFO"); + + setApplicationName(applicationInfo.value("name").toString()); + setApplicationVersion(applicationInfo.value("version").toString()); + setOrganizationName(applicationInfo.value("organizationName").toString()); + setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); + +#if defined(Q_WS_MAC) && defined(QT_NO_DEBUG) + // if this is a release OS X build use fervor to check for an update + FvUpdater::sharedUpdater()->SetFeedURL("file:///Users/birarda/Desktop/Appcast.xml"); + FvUpdater::sharedUpdater()->CheckForUpdatesNotSilent(); +#endif initMenu(); From 949395348f64966c8971bd37fe7afb90ebcd0688 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Jun 2013 10:42:07 -0700 Subject: [PATCH 05/10] switch to silent update checking --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6d94f361b3..a29e36f230 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -267,7 +267,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : #if defined(Q_WS_MAC) && defined(QT_NO_DEBUG) // if this is a release OS X build use fervor to check for an update FvUpdater::sharedUpdater()->SetFeedURL("file:///Users/birarda/Desktop/Appcast.xml"); - FvUpdater::sharedUpdater()->CheckForUpdatesNotSilent(); + FvUpdater::sharedUpdater()->CheckForUpdatesSilent(); #endif initMenu(); From 383b6ecec242ecefc9870f84f918adedd3d481a3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Jun 2013 11:27:24 -0700 Subject: [PATCH 06/10] use correct future appcast URL --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a29e36f230..cd04b604eb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -266,7 +266,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : #if defined(Q_WS_MAC) && defined(QT_NO_DEBUG) // if this is a release OS X build use fervor to check for an update - FvUpdater::sharedUpdater()->SetFeedURL("file:///Users/birarda/Desktop/Appcast.xml"); + FvUpdater::sharedUpdater()->SetFeedURL("https://s3-us-west-1.amazonaws.com/highfidelity/appcast.xml"); FvUpdater::sharedUpdater()->CheckForUpdatesSilent(); #endif From 2c7d720e8872934610cf1d7328084dcbe0c4bccd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Jun 2013 15:52:01 -0700 Subject: [PATCH 07/10] add a boolean helper for _owningAvatar NULL comparison --- interface/src/Avatar.cpp | 34 +++++++++++++++++----------------- interface/src/Avatar.h | 2 ++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/interface/src/Avatar.cpp b/interface/src/Avatar.cpp index a603dbd472..1bc9013bd6 100644 --- a/interface/src/Avatar.cpp +++ b/interface/src/Avatar.cpp @@ -431,7 +431,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { glm::vec3 right = orientation * IDENTITY_RIGHT; // Update movement timers - if (!_owningAgent) { + if (isMyAvatar()) { _elapsedTimeSinceCollision += deltaTime; const float VELOCITY_MOVEMENT_TIMER_THRESHOLD = 0.2f; if (glm::length(_velocity) < VELOCITY_MOVEMENT_TIMER_THRESHOLD) { @@ -444,14 +444,14 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { } // Collect thrust forces from keyboard and devices - if (!_owningAgent) { + if (isMyAvatar()) { updateThrust(deltaTime, transmitter); } // copy velocity so we can use it later for acceleration glm::vec3 oldVelocity = getVelocity(); - if (!_owningAgent) { + if (isMyAvatar()) { // update position by velocity _position += _velocity * deltaTime; @@ -460,7 +460,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { } //figure out if the mouse cursor is over any body spheres... - if (!_owningAgent) { + if (isMyAvatar) { checkForMouseRayTouching(); } @@ -503,12 +503,12 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { // if this is not my avatar, then hand position comes from transmitted data - if (_owningAgent) { + if (!isMyAvatar()) { _skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position = _handPosition; } //detect and respond to collisions with other avatars... - if (!_owningAgent) { + if (isMyAvatar()) { updateAvatarCollisions(deltaTime); } @@ -517,10 +517,10 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { _avatarTouch.simulate(deltaTime); // apply gravity and collision with the ground/floor - if (!_owningAgent && USING_AVATAR_GRAVITY) { + if (isMyAvatar() && USING_AVATAR_GRAVITY) { _velocity += _gravity * (GRAVITY_EARTH * deltaTime); } - if (!_owningAgent) { + if (isMyAvatar()) { updateCollisionWithEnvironment(); } @@ -533,11 +533,11 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { } // collision response with voxels - if (!_owningAgent) { + if (isMyAvatar()) { updateCollisionWithVoxels(); } - if (!_owningAgent) { + if (isMyAvatar()) { // add thrust to velocity _velocity += _thrust * deltaTime; @@ -650,7 +650,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { _head.setPosition(_bodyBall[ BODY_BALL_HEAD_BASE ].position); _head.setScale (_bodyBall[ BODY_BALL_HEAD_BASE ].radius); _head.setSkinColor(glm::vec3(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2])); - _head.simulate(deltaTime, !_owningAgent); + _head.simulate(deltaTime, isMyAvatar()); // use speed and angular velocity to determine walking vs. standing if (_speed + fabs(_bodyYawDelta) > 0.2) { @@ -709,7 +709,7 @@ void Avatar::updateHandMovementAndTouching(float deltaTime) { _skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position += transformedHandMovement; - if (!_owningAgent) { + if (isMyAvatar()) { _avatarTouch.setMyBodyPosition(_position); _avatarTouch.setMyOrientation(orientation); @@ -801,7 +801,7 @@ void Avatar::updateHandMovementAndTouching(float deltaTime) { updateArmIKAndConstraints(deltaTime); //Set right hand position and state to be transmitted, and also tell AvatarTouch about it - if (!_owningAgent) { + if (isMyAvatar()) { setHandPosition(_skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position); if (_mousePressed) { @@ -985,7 +985,7 @@ void Avatar::setGravity(glm::vec3 gravity) { void Avatar::render(bool lookingInMirror, bool renderAvatarBalls) { - if (!_owningAgent && usingBigSphereCollisionTest) { + if (isMyAvatar() && usingBigSphereCollisionTest) { // show TEST big sphere glColor4f(0.5f, 0.6f, 0.8f, 0.7); glPushMatrix(); @@ -1002,7 +1002,7 @@ void Avatar::render(bool lookingInMirror, bool renderAvatarBalls) { renderBody(lookingInMirror, renderAvatarBalls); // if this is my avatar, then render my interactions with the other avatar - if (!_owningAgent) { + if (isMyAvatar()) { _avatarTouch.render(Application::getInstance()->getCamera()->getPosition()); } @@ -1204,7 +1204,7 @@ float Avatar::getBallRenderAlpha(int ball, bool lookingInMirror) const { const float RENDER_OPAQUE_OUTSIDE = 1.25f; // render opaque if greater than this distance const float DO_NOT_RENDER_INSIDE = 0.75f; // do not render if less than this distance float distanceToCamera = glm::length(Application::getInstance()->getCamera()->getPosition() - _bodyBall[ball].position); - return (lookingInMirror || _owningAgent) ? 1.0f : glm::clamp( + return (lookingInMirror || !isMyAvatar()) ? 1.0f : glm::clamp( (distanceToCamera - DO_NOT_RENDER_INSIDE) / (RENDER_OPAQUE_OUTSIDE - DO_NOT_RENDER_INSIDE), 0.f, 1.f); } @@ -1222,7 +1222,7 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { } } else if (alpha > 0.0f) { // Render the body ball sphere - if (_owningAgent || b == BODY_BALL_RIGHT_ELBOW + if (!isMyAvatar() || b == BODY_BALL_RIGHT_ELBOW || b == BODY_BALL_RIGHT_WRIST || b == BODY_BALL_RIGHT_FINGERTIPS ) { glColor3f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f, diff --git a/interface/src/Avatar.h b/interface/src/Avatar.h index b7d424de29..d0861ff8a0 100644 --- a/interface/src/Avatar.h +++ b/interface/src/Avatar.h @@ -157,6 +157,8 @@ private: // privatize copy constructor and assignment operator to avoid copying Avatar(const Avatar&); Avatar& operator= (const Avatar&); + + bool isMyAvatar() const { return _owningAgent == NULL } struct AvatarBall { From 6c4dd038678c8603df69138866e9dad9b2ffec5e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Jun 2013 15:52:26 -0700 Subject: [PATCH 08/10] add a missing semicolon --- interface/src/Avatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Avatar.h b/interface/src/Avatar.h index d0861ff8a0..eb04b2c8d9 100644 --- a/interface/src/Avatar.h +++ b/interface/src/Avatar.h @@ -158,7 +158,7 @@ private: Avatar(const Avatar&); Avatar& operator= (const Avatar&); - bool isMyAvatar() const { return _owningAgent == NULL } + bool isMyAvatar() const { return _owningAgent == NULL; } struct AvatarBall { From fd3072522228422d185a319b4917f7719bb12b82 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Jun 2013 15:53:35 -0700 Subject: [PATCH 09/10] add a missing set of parenthesis --- interface/src/Avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Avatar.cpp b/interface/src/Avatar.cpp index 1bc9013bd6..2084ad01b0 100644 --- a/interface/src/Avatar.cpp +++ b/interface/src/Avatar.cpp @@ -460,7 +460,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { } //figure out if the mouse cursor is over any body spheres... - if (isMyAvatar) { + if (isMyAvatar()) { checkForMouseRayTouching(); } From 99fddab590ca07554647b942743f6f53aefd158b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 28 Jun 2013 13:01:21 -0700 Subject: [PATCH 10/10] an initial revision of the podspec for Base8 --- hifi.podspec | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 hifi.podspec diff --git a/hifi.podspec b/hifi.podspec new file mode 100644 index 0000000000..06f65aa322 --- /dev/null +++ b/hifi.podspec @@ -0,0 +1,89 @@ +# +# Be sure to run `pod spec lint hifi.podspec' to ensure this is a +# valid spec and remove all comments before submitting the spec. +# +# To learn more about the attributes see http://docs.cocoapods.org/specification.html +# +Pod::Spec.new do |s| + s.name = "hifi" + s.version = "0.0.1" + s.summary = "Test platform for various render and interface tests for next-gen VR system." + + s.homepage = "https://github.com/worklist/hifi" + + # Specify the license type. CocoaPods detects automatically the license file if it is named + # 'LICENCE*.*' or 'LICENSE*.*', however if the name is different, specify it. + # s.license = 'MIT (example)' + + # Specify the authors of the library, with email addresses. You can often find + # the email addresses of the authors by using the SCM log. E.g. $ git log + # + s.author = { "Worklist" => "contact@worklist.net" } + + # Specify the location from where the source should be retrieved. + # + s.source = { :git => "https://github.com/worklist/hifi.git", :commit => "ddc85272b939922c32e6a80f763e6de3cb00ab1a" } + + s.platform = :ios + s.ios.deployment_target = "6.0" + + # A list of file patterns which select the source files that should be + # added to the Pods project. If the pattern is a directory then the + # path will automatically have '*.{h,m,mm,c,cpp}' appended. + # + # s.source_files = 'Classes', 'Classes/**/*.{h,m}' + # s.exclude_files = 'Classes/Exclude' + + s.subspec "shared" do |sp| + sp.source_files = "libraries/shared/src" + sp.public_header_files = "librares/shared/src" + sp.exclude_files = "libraries/shared/src/UrlReader.*" + end + + s.subspec "audio" do |sp| + sp.source_files = "libraries/audio/src" + sp.public_header_files = "libraries/audio/src" + sp.xcconfig = { 'CLANG_CXX_LIBRARY' => "libc++" } + sp.dependency 'glm' + end + + # A list of file patterns which select the header files that should be + # made available to the application. If the pattern is a directory then the + # path will automatically have '*.h' appended. + # + # If you do not explicitly set the list of public header files, + # all headers of source_files will be made public. + # + # s.public_header_files = 'Classes/**/*.h' + + # A list of paths to preserve after installing the Pod. + # CocoaPods cleans by default any file that is not used. + # Please don't include documentation, example, and test files. + # + # s.preserve_paths = "FilesToSave", "MoreFilesToSave" + + # Specify a list of frameworks that the application needs to link + # against for this Pod to work. + # + # s.framework = 'SomeFramework' + # s.frameworks = 'SomeFramework', 'AnotherFramework' + + # Specify a list of libraries that the application needs to link + # against for this Pod to work. + # + # s.library = 'iconv' + # s.libraries = 'iconv', 'xml2' + + # If this Pod uses ARC, specify it like so. + # + s.requires_arc = false + + # If you need to specify any other build settings, add them to the + # xcconfig hash. + # + # s.xcconfig = { 'CLANG_CXX_LIBRARY' => "libc++" } + + # Finally, specify any Pods that this Pod depends on. + # + # s.dependency 'JSONKit', '~> 1.4' +end