Add support for CAB archives to rbutil

Change-Id: Ia8b4953343caf8bc2b3c5a6cfd53c921c6d082b1
Reviewed-on: http://gerrit.rockbox.org/418
Reviewed-by: Dominik Riebeling <Dominik.Riebeling@gmail.com>
diff --git a/rbutil/rbutilqt/base/archiveutil.cpp b/rbutil/rbutilqt/base/archiveutil.cpp
new file mode 100644
index 0000000..d5f0a12
--- /dev/null
+++ b/rbutil/rbutilqt/base/archiveutil.cpp
@@ -0,0 +1,30 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ *   Copyright (C) 2013 Amaury Pouly
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <QtCore>
+#include <QDebug>
+#include "archiveutil.h"
+
+ArchiveUtil::ArchiveUtil(QObject* parent)
+    :QObject(parent)
+{
+}
+
+ArchiveUtil::~ArchiveUtil()
+{
+}
diff --git a/rbutil/rbutilqt/base/archiveutil.h b/rbutil/rbutilqt/base/archiveutil.h
new file mode 100644
index 0000000..7661672
--- /dev/null
+++ b/rbutil/rbutilqt/base/archiveutil.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ *   Copyright (C) 2013 Amaury Pouly
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef ARCHIVEUTIL_H
+#define ARCHIVEUTIL_H
+
+#include <QtCore>
+
+class ArchiveUtil : public QObject
+{
+    Q_OBJECT
+
+    public:
+        ArchiveUtil(QObject* parent);
+        ~ArchiveUtil();
+        virtual bool close(void) = 0;
+        virtual bool extractArchive(const QString& dest, QString file = "") = 0;
+        virtual QStringList files(void) = 0;
+
+    signals:
+       void logProgress(int, int);
+       void logItem(QString, int);
+};
+#endif
+
+ 
diff --git a/rbutil/rbutilqt/base/bootloaderinstallbase.cpp b/rbutil/rbutilqt/base/bootloaderinstallbase.cpp
index 6cfb2cd..1a47f96 100644
--- a/rbutil/rbutilqt/base/bootloaderinstallbase.cpp
+++ b/rbutil/rbutilqt/base/bootloaderinstallbase.cpp
@@ -22,6 +22,7 @@
 #include "bootloaderinstallbase.h"
 #include "utils.h"
 #include "ziputil.h"
+#include "mspackutil.h"
 
 #if defined(Q_OS_MACX)
 #include <sys/param.h>
@@ -215,11 +216,34 @@
 bool BootloaderInstallBase::setOfFile(QString of, QStringList blfile)
 {
     bool found = false;
-    ZipUtil z(this);
-    // check if the file set is in zip format
-    if(z.open(of)) {
+    ArchiveUtil *util = 0;
+
+    // try ZIP first
+    ZipUtil *zu = new ZipUtil(this);
+    if(zu->open(of))
+    {
         emit logItem(tr("Zip file format detected"), LOGINFO);
-        QStringList contents = z.files();
+        util = zu;
+    }
+    else
+        delete zu;
+
+    // if ZIP failed, try CAB
+    if(util == 0)
+    {
+        MsPackUtil *msu = new MsPackUtil(this);
+        if(msu->open(of))
+        {
+            emit logItem(tr("CAB file format detected"), LOGINFO);
+            util = msu;
+        }
+        else
+            delete msu;
+    }
+
+    // check if the file set is in zip format
+    if(util) {
+        QStringList contents = util->files();
         qDebug() << "[BootloaderInstallBase] archive contains:" << contents;
         for(int i = 0; i < blfile.size(); ++i) {
             // strip any path, we don't know the structure in the zip
@@ -237,7 +261,7 @@
                     m_tempof.open();
                     m_offile = m_tempof.fileName();
                     m_tempof.close();
-                    if(!z.extractArchive(m_offile, contents.at(j))) {
+                    if(!util->extractArchive(m_offile, contents.at(j))) {
                         emit logItem(tr("Error extracting firmware from archive"), LOGERROR);
                         found = false;
                         break;
@@ -249,12 +273,13 @@
         if(!found) {
             emit logItem(tr("Could not find firmware in archive"), LOGERROR);
         }
-
+        delete util;
     }
     else {
         m_offile = of;
         found = true;
     }
+
     return found;
 }
 
diff --git a/rbutil/rbutilqt/base/mspackutil.cpp b/rbutil/rbutilqt/base/mspackutil.cpp
new file mode 100644
index 0000000..4bc7307
--- /dev/null
+++ b/rbutil/rbutilqt/base/mspackutil.cpp
@@ -0,0 +1,163 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ *   Copyright (C) 2013 Amaury Pouly
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <QtCore>
+#include <QDebug>
+#include "mspackutil.h"
+#include "progressloggerinterface.h"
+
+MsPackUtil::MsPackUtil(QObject* parent)
+    :ArchiveUtil(parent)
+{
+    m_cabd = mspack_create_cab_decompressor(NULL);
+    m_cabinet = NULL;
+    if(!m_cabd)
+        qDebug() << "[MsPackUtil] CAB decompressor creation failed!";
+}
+
+MsPackUtil::~MsPackUtil()
+{
+    close();
+    if(m_cabd)
+        mspack_destroy_cab_decompressor(m_cabd);
+}
+
+bool MsPackUtil::open(QString& mspackfile)
+{
+    close();
+
+    if(m_cabd == NULL)
+    {
+        qDebug() << "[MsPackUtil] No CAB decompressor available: cannot open file!";
+        return false;
+    }
+    m_cabinet = m_cabd->search(m_cabd, QFile::encodeName(mspackfile).constData());
+    return m_cabinet != NULL;
+}
+
+bool MsPackUtil::close(void)
+{
+    if(m_cabd && m_cabinet)
+        m_cabd->close(m_cabd, m_cabinet);
+    m_cabinet = NULL;
+    return true;
+}
+
+bool MsPackUtil::extractArchive(const QString& dest, QString file)
+{
+    qDebug() << "[MsPackUtil] extractArchive" << dest << file;
+    if(!m_cabinet)
+    {
+        qDebug() << "[MsPackUtil] CAB file not open!";
+        return false;
+    }
+
+    // construct the filename when extracting a single file from an archive.
+    // if the given destination is a full path use it as output name,
+    // otherwise use it as path to place the file as named in the archive.
+    QString singleoutfile;
+    if(!file.isEmpty() && QFileInfo(dest).isDir())
+        singleoutfile = dest + "/" + file;
+    else if(!file.isEmpty())
+        singleoutfile = dest;
+    struct mscabd_file *f = m_cabinet->files;
+    if(f == NULL)
+    {
+        qDebug() << "[MsPackUtil] CAB doesn't contain file" << file;
+        return true;
+    }
+    bool found = false;
+    while(f)
+    {
+        QString name = QFile::decodeName(f->filename);
+        name.replace("\\", "/");
+        if(name.at(0) == '/')
+            name.remove(0, 1);
+        if(name == file || file.isEmpty())
+        {
+            QString path;
+            if(!singleoutfile.isEmpty())
+                path = singleoutfile;
+            else
+                path = dest + "/" + name;
+            // make sure the output path exists
+            if(!QDir().mkpath(QFileInfo(path).absolutePath()))
+            {
+                emit logItem(tr("Creating output path failed"), LOGERROR);
+                qDebug() << "[MsPackUtil] creating output path failed for:" << path;
+                emit logProgress(1, 1);
+                return false;
+            }
+            int ret = m_cabd->extract(m_cabd, f, QFile::encodeName(path).constData());
+            if(ret != MSPACK_ERR_OK)
+            {
+                emit logItem(tr("Error during CAB operation"), LOGERROR);
+                qDebug() << "[MsPackUtil] mspack error: " << ret << "(" << errorStringMsPack(ret) << ")";
+                emit logProgress(1, 1);
+                return false;
+            }
+            found = true;
+        }
+        f = f->next;
+    }
+    emit logProgress(1, 1);
+
+    return found;
+}
+
+QStringList MsPackUtil::files(void)
+{
+    QStringList list;
+    if(!m_cabinet)
+    {
+        qDebug() << "[MsPackUtil] CAB file not open!";
+        return list;
+    }
+    struct mscabd_file *file = m_cabinet->files;
+    while(file)
+    {
+        QString name = QFile::decodeName(file->filename);
+        name.replace("\\", "/");
+        if(name.at(0) == '/')
+            name.remove(0, 1);
+        list.append(name);
+        file = file->next;
+    }
+
+    return list;
+}
+
+QString MsPackUtil::errorStringMsPack(int error) const
+{
+    switch(error)
+    {
+        case MSPACK_ERR_OK: return "Ok";
+        case MSPACK_ERR_ARGS: return "Bad arguments";
+        case MSPACK_ERR_OPEN: return "Open error";
+        case MSPACK_ERR_READ: return "Read error";
+        case MSPACK_ERR_WRITE: return "Write error";
+        case MSPACK_ERR_SEEK: return "Seek error";
+        case MSPACK_ERR_NOMEMORY: return "Out of memory";
+        case MSPACK_ERR_SIGNATURE: return "Bad signature";
+        case MSPACK_ERR_DATAFORMAT: return "Bad data format";
+        case MSPACK_ERR_CHECKSUM: return "Checksum error";
+        case MSPACK_ERR_CRUNCH: return "Compression error";
+        case MSPACK_ERR_DECRUNCH: return "Decompression error";
+        default: return "Unknown";
+    }
+}
diff --git a/rbutil/rbutilqt/base/mspackutil.h b/rbutil/rbutilqt/base/mspackutil.h
new file mode 100644
index 0000000..1cb4350
--- /dev/null
+++ b/rbutil/rbutilqt/base/mspackutil.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ *   Copyright (C) 2013 Amaury Pouly
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef MSPACKUTIL_H
+#define MSPACKUTIL_H
+
+#include <QtCore>
+#include "archiveutil.h"
+#include "mspack/mspack.h"
+
+class MsPackUtil : public ArchiveUtil
+{
+    Q_OBJECT
+
+    public:
+        // archive types can be ORed
+        MsPackUtil(QObject* parent);
+        ~MsPackUtil();
+        bool open(QString& mspackfile);
+        virtual bool close(void);
+        virtual bool extractArchive(const QString& dest, QString file = "");
+        virtual QStringList files(void);
+
+    signals:
+       void logProgress(int, int);
+       void logItem(QString, int);
+
+    private:
+        QString errorStringMsPack(int error) const;
+        struct mscab_decompressor* m_cabd;
+        struct mscabd_cabinet *m_cabinet;
+
+};
+#endif
+
+ 
diff --git a/rbutil/rbutilqt/base/ziputil.cpp b/rbutil/rbutilqt/base/ziputil.cpp
index ca921eb..b93d5fd 100644
--- a/rbutil/rbutilqt/base/ziputil.cpp
+++ b/rbutil/rbutilqt/base/ziputil.cpp
@@ -26,7 +26,7 @@
 #include "quazip/quazipfileinfo.h"
 
 
-ZipUtil::ZipUtil(QObject* parent) : QObject(parent)
+ZipUtil::ZipUtil(QObject* parent) : ArchiveUtil(parent)
 {
     m_zip = NULL;
 }
@@ -74,7 +74,7 @@
 //!             single file.
 //! @brief file file to extract from archive, full archive if empty.
 //! @return true on success, false otherwise
-bool ZipUtil::extractArchive(QString& dest, QString file)
+bool ZipUtil::extractArchive(const QString& dest, QString file)
 {
     qDebug() << "[ZipUtil] extractArchive" << dest << file;
     bool result = true;
diff --git a/rbutil/rbutilqt/base/ziputil.h b/rbutil/rbutilqt/base/ziputil.h
index 49a1bd3..25c3dce 100644
--- a/rbutil/rbutilqt/base/ziputil.h
+++ b/rbutil/rbutilqt/base/ziputil.h
@@ -20,11 +20,12 @@
 #define ZIPUTIL_H
 
 #include <QtCore>
+#include "archiveutil.h"
 #include "quazip/quazip.h"
 #include "quazip/quazipfile.h"
 #include "quazip/quazipfileinfo.h"
 
-class ZipUtil : public QObject
+class ZipUtil : public ArchiveUtil
 {
     Q_OBJECT
 
@@ -32,12 +33,12 @@
         ZipUtil(QObject* parent);
         ~ZipUtil();
         bool open(QString& zipfile, QuaZip::Mode mode = QuaZip::mdUnzip);
-        bool close(void);
-        bool extractArchive(QString& dest, QString file = "");
+        virtual bool close(void);
+        virtual bool extractArchive(const QString& dest, QString file = "");
         bool appendDirToArchive(QString& source, QString& basedir);
         bool appendFileToArchive(QString& file, QString& basedir);
         qint64 totalUncompressedSize(unsigned int clustersize = 0);
-        QStringList files(void);
+        virtual QStringList files(void);
 
     signals:
        void logProgress(int, int);
diff --git a/rbutil/rbutilqt/rbutilqt.pri b/rbutil/rbutilqt/rbutilqt.pri
index 8ec961a..b8193d5 100644
--- a/rbutil/rbutilqt/rbutilqt.pri
+++ b/rbutil/rbutilqt/rbutilqt.pri
@@ -79,7 +79,14 @@
  gui/comboboxviewdelegate.cpp \
  gui/selectiveinstallwidget.cpp \
  gui/backupdialog.cpp \
- gui/changelog.cpp
+ gui/changelog.cpp \
+ mspack/cabd.c \
+ mspack/lzxd.c \
+ mspack/mszipd.c \
+ mspack/qtmd.c \
+ mspack/system-mspack.c \
+ base/mspackutil.cpp \
+ base/archiveutil.cpp \
 
 
 HEADERS += \
@@ -157,7 +164,25 @@
  gui/comboboxviewdelegate.h \
  gui/selectiveinstallwidget.h \
  gui/backupdialog.h \
- gui/changelog.h
+ gui/changelog.h \
+ mspack/cab.h \
+ mspack/chm.h \
+ mspack/des.h \
+ mspack/hlp.h \
+ mspack/kwaj.h \
+ mspack/lit.h \
+ mspack/lzss.h \
+ mspack/lzx.h \
+ mspack/mspack.h \
+ mspack/mszip.h \
+ mspack/qtm.h \
+ mspack/readbits.h \
+ mspack/readhuff.h \
+ mspack/sha.h \
+ mspack/system-mspack.h \
+ mspack/szdd.h \
+ base/mspackutil.h \
+ base/archiveutil.h \
 
 
 FORMS += \