From 4d983577dcb3cd476c363eb366b2576d11631352 Mon Sep 17 00:00:00 2001
From: kervala <none@none>
Date: Mon, 16 May 2016 11:10:01 +0200
Subject: [PATCH] Changed: One class per files operation

--HG--
branch : develop
---
 .../client/ryzom_installer/src/archive.h      | 100 -----
 .../ryzom_installer/src/filescleaner.cpp      |  79 ++++
 .../client/ryzom_installer/src/filescleaner.h |  45 ++
 .../ryzom_installer/src/filescopier.cpp       | 188 ++++++++
 .../client/ryzom_installer/src/filescopier.h  |  67 +++
 .../src/{archive.cpp => filesextractor.cpp}   | 413 +++++-------------
 .../ryzom_installer/src/filesextractor.h      |  53 +++
 .../client/ryzom_installer/src/operation.h    |  37 ++
 8 files changed, 571 insertions(+), 411 deletions(-)
 delete mode 100644 code/ryzom/tools/client/ryzom_installer/src/archive.h
 create mode 100644 code/ryzom/tools/client/ryzom_installer/src/filescleaner.cpp
 create mode 100644 code/ryzom/tools/client/ryzom_installer/src/filescleaner.h
 create mode 100644 code/ryzom/tools/client/ryzom_installer/src/filescopier.cpp
 create mode 100644 code/ryzom/tools/client/ryzom_installer/src/filescopier.h
 rename code/ryzom/tools/client/ryzom_installer/src/{archive.cpp => filesextractor.cpp} (60%)
 create mode 100644 code/ryzom/tools/client/ryzom_installer/src/filesextractor.h
 create mode 100644 code/ryzom/tools/client/ryzom_installer/src/operation.h

diff --git a/code/ryzom/tools/client/ryzom_installer/src/archive.h b/code/ryzom/tools/client/ryzom_installer/src/archive.h
deleted file mode 100644
index a442056e1..000000000
--- a/code/ryzom/tools/client/ryzom_installer/src/archive.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
-// Copyright (C) 2010  Winch Gate Property Limited
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#ifndef ARCHIVE_H
-#define ARCHIVE_H
-
-/**
- * Files copy, decompression, extraction
- *
- * \author Cedric 'Kervala' OCHS
- * \date 2016
- */
-class CArchive : public QObject
-{
-	Q_OBJECT
-
-public:
-	CArchive(QObject *parent = NULL);
-	virtual ~CArchive();
-
-	bool extract(const QString &filename, const QString &dest);
-	bool copyServerFiles(const QString &src, const QString &dst);
-	bool copyProfileFiles(const QString &src, const QString &dst);
-	bool cleanServerFiles(const QString &directory);
-
-	void stop();
-	bool mustStop();
-
-signals:
-	// emitted when requesting real URL
-	void extractPrepare();
-
-	// emitted when we got the initial (local) and total (remote) size of file
-	void extractInit(qint64 current, qint64 total);
-
-	// emitted when we begin to download
-	void extractStart();
-
-	// emitted when the download stopped
-	void extractStop();
-
-	// emitted when extracting
-	void extractProgress(qint64 current, const QString &filename);
-
-	// emitted when the whole file is downloaded
-	void extractSuccess(qint64 total);
-
-	// emitted when an error occurs
-	void extractFail(const QString &error);
-
-	// emitted when done and should process next step
-	void done();
-
-protected:
-
-	struct FileToCopy
-	{
-		QString filename;
-		QString src;
-		QString dst;
-		qint64 size;
-		QDateTime date;
-	};
-
-	typedef QList<FileToCopy> FilesToCopy;
-
-	bool extract7z();
-	bool extractZip();
-	bool extractBnp();
-
-	bool progress(const std::string &filename, uint32 currentFile, uint32 totalFiles);
-
-	bool copyServerFiles();
-	bool copyProfileFiles();
-	bool copyFiles(const FilesToCopy &files);
-
-	static void getFilesList(const QString &srcDir, const QString &dstDir, const QStringList &filter, FilesToCopy &files);
-
-	QString m_filename;
-	QString m_dest;
-
-	QMutex m_mutex;
-
-	bool m_mustStop;
-};
-
-#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/filescleaner.cpp b/code/ryzom/tools/client/ryzom_installer/src/filescleaner.cpp
new file mode 100644
index 000000000..4b66b437b
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/filescleaner.cpp
@@ -0,0 +1,79 @@
+// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
+// Copyright (C) 2010  Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "stdpch.h"
+#include "filescleaner.h"
+#include "operation.h"
+#include "utils.h"
+
+#include "nel/misc/big_file.h"
+#include "nel/misc/callback.h"
+#include "nel/misc/file.h"
+#include "nel/misc/path.h"
+
+#include "7z.h"
+#include "7zAlloc.h"
+#include "7zBuf.h"
+#include "7zCrc.h"
+
+#include "qzipreader.h"
+
+#include <sys/stat.h>
+
+#ifdef DEBUG_NEW
+	#define new DEBUG_NEW
+#endif
+
+CFilesCleaner::CFilesCleaner(IOperationProgressListener *listener):m_listener(listener)
+{
+}
+
+CFilesCleaner::~CFilesCleaner()
+{
+}
+
+void CFilesCleaner::setDirectory(const QString &src)
+{
+	m_directory = src;
+}
+
+bool CFilesCleaner::exec()
+{
+	QDir dir(m_directory);
+
+	// directory doesn't exist
+	if (!dir.exists()) return false;
+
+	if (!dir.cd("data") && dir.exists()) return false;
+
+	// temporary files
+	QStringList files = dir.entryList(QStringList() << "*.string_cache" << "*.packed_sheets" << "*.packed" << "*.pem", QDir::Files);
+
+	foreach(const QString &file, files)
+	{
+		dir.remove(file);
+	}
+
+	// fonts directory is not needed anymore
+	if (dir.cd("fonts") && dir.exists())
+	{
+		dir.removeRecursively();
+	}
+
+	if (m_listener) m_listener->operationFinish();
+
+	return true;
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/filescleaner.h b/code/ryzom/tools/client/ryzom_installer/src/filescleaner.h
new file mode 100644
index 000000000..6458b5742
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/filescleaner.h
@@ -0,0 +1,45 @@
+// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
+// Copyright (C) 2010  Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef FILESCLEANER_H
+#define FILESCLEANER_H
+
+class IOperationProgressListener;
+
+/**
+ * Files cleaner
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CFilesCleaner
+{
+public:
+	CFilesCleaner(IOperationProgressListener *listener);
+	virtual ~CFilesCleaner();
+
+	void setDirectory(const QString &src);
+
+	bool exec();
+
+protected:
+
+	IOperationProgressListener *m_listener;
+
+	QString m_directory;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/filescopier.cpp b/code/ryzom/tools/client/ryzom_installer/src/filescopier.cpp
new file mode 100644
index 000000000..8c8cfabb4
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/filescopier.cpp
@@ -0,0 +1,188 @@
+// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
+// Copyright (C) 2010  Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "stdpch.h"
+#include "filescopier.h"
+#include "utils.h"
+#include "operation.h"
+
+#include "nel/misc/path.h"
+
+#ifdef DEBUG_NEW
+	#define new DEBUG_NEW
+#endif
+
+CFilesCopier::CFilesCopier(IOperationProgressListener *listener):m_listener(listener)
+{
+}
+
+CFilesCopier::~CFilesCopier()
+{
+}
+
+void CFilesCopier::setSourceDirectory(const QString &src)
+{
+	m_sourceDirectory = src;
+}
+
+void CFilesCopier::setDesinationDirectory(const QString &dst)
+{
+	m_destinationDirectory = dst;
+}
+
+void CFilesCopier::setIncludeFilter(const QStringList &filter)
+{
+	m_includeFilter = filter;
+}
+
+void CFilesCopier::setExcludeFilter(const QStringList &filter)
+{
+	m_excludeFilter = filter;
+}
+
+bool CFilesCopier::exec()
+{
+	if (m_sourceDirectory.isEmpty() || m_destinationDirectory.isEmpty()) return false;
+
+	if (m_listener) m_listener->operationPrepare();
+
+	QDir().mkpath(m_destinationDirectory);
+
+	FilesToCopy files;
+
+	CFilesCopier::getFilesList(files);
+
+	return copyFiles(files);
+}
+
+void CFilesCopier::getFilesList(FilesToCopy &files)
+{
+	QDir dir(m_sourceDirectory);
+
+	QFileInfoList entries = dir.entryInfoList(m_includeFilter);
+
+	foreach(const QFileInfo &entry, entries)
+	{
+		QString fullPath = entry.absoluteFilePath();
+
+		QString dstPath = m_destinationDirectory + "/" + dir.relativeFilePath(fullPath);
+
+		if (entry.isDir())
+		{
+			QDir().mkpath(dstPath);
+
+			QDir subDir(fullPath);
+
+			QDirIterator it(subDir, QDirIterator::Subdirectories);
+
+			while (it.hasNext())
+			{
+				fullPath = it.next();
+
+				if (it.fileName().startsWith('.')) continue;
+
+				QFileInfo fileInfo = it.fileInfo();
+
+				dstPath = m_destinationDirectory + "/" + dir.relativeFilePath(fullPath);
+
+				if (fileInfo.isDir())
+				{
+					QDir().mkpath(dstPath);
+				}
+				else
+				{
+					FileToCopy file;
+					file.filename = it.fileName();
+					file.src = it.filePath();
+					file.dst = dstPath;
+					file.size = it.fileInfo().size();
+					file.date = it.fileInfo().lastModified().toTime_t();
+
+					files << file;
+				}
+			}
+		}
+		else
+		{
+			FileToCopy file;
+			file.filename = entry.fileName();
+			file.src = entry.filePath();
+			file.dst = dstPath;
+			file.size = entry.size();
+			file.date = entry.lastModified().toTime_t();
+
+			files << file;
+		}
+	}
+}
+
+bool CFilesCopier::copyFiles(const FilesToCopy &files)
+{
+	qint64 totalSize = 0;
+
+	foreach(const FileToCopy &file, files)
+	{
+		totalSize += file.size;
+	}
+
+	if (m_listener)
+	{
+		m_listener->operationInit(0, totalSize);
+		m_listener->operationStart();
+	}
+
+	qint64 processedSize = 0;
+
+	foreach(const FileToCopy &file, files)
+	{
+		if (m_listener && m_listener->operationShouldStop())
+		{
+			m_listener->operationStop();
+			return true;
+		}
+
+		if (m_listener) m_listener->operationProgress(processedSize, file.filename);
+
+		QFileInfo dstFileInfo(file.dst);
+
+		if (dstFileInfo.size() != file.size || dstFileInfo.lastModified().toTime_t() != file.date)
+		{
+			// force deleting previous file since it was incomplete
+			QFile::remove(file.dst);
+
+			if (!QFile::copy(file.src, file.dst))
+			{
+				if (m_listener) m_listener->operationFail(QApplication::tr("Unable to copy file %1").arg(file.src));
+				return false;
+			}
+
+			if (!NLMISC::CFile::setFileModificationDate(qToUtf8(file.dst), file.date))
+			{
+				qDebug() << "Unable to change date of " << file.dst;
+			}
+		}
+
+		processedSize += file.size;
+	}
+
+	if (m_listener)
+	{
+		m_listener->operationSuccess(totalSize);
+		m_listener->operationFinish();
+	}
+
+	return true;
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/filescopier.h b/code/ryzom/tools/client/ryzom_installer/src/filescopier.h
new file mode 100644
index 000000000..0148bfdaf
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/filescopier.h
@@ -0,0 +1,67 @@
+// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
+// Copyright (C) 2010  Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef FILESCOPIER_H
+#define FILESCOPIER_H
+
+class IOperationProgressListener;
+
+/**
+ * Files copier
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CFilesCopier
+{
+public:
+	CFilesCopier(IOperationProgressListener *listener);
+	virtual ~CFilesCopier();
+
+	void setSourceDirectory(const QString &src);
+	void setDesinationDirectory(const QString &src);
+
+	void setIncludeFilter(const QStringList &filter);
+	void setExcludeFilter(const QStringList &filter);
+
+	bool exec();
+
+protected:
+
+	struct FileToCopy
+	{
+		QString filename;
+		QString src;
+		QString dst;
+		qint64 size;
+		uint date;
+	};
+
+	typedef QList<FileToCopy> FilesToCopy;
+
+	void getFilesList(FilesToCopy &files);
+	bool copyFiles(const FilesToCopy &files);
+
+	IOperationProgressListener *m_listener;
+
+	QString m_sourceDirectory;
+	QString m_destinationDirectory;
+
+	QStringList m_includeFilter;
+	QStringList m_excludeFilter;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/archive.cpp b/code/ryzom/tools/client/ryzom_installer/src/filesextractor.cpp
similarity index 60%
rename from code/ryzom/tools/client/ryzom_installer/src/archive.cpp
rename to code/ryzom/tools/client/ryzom_installer/src/filesextractor.cpp
index 425e4f379..222add173 100644
--- a/code/ryzom/tools/client/ryzom_installer/src/archive.cpp
+++ b/code/ryzom/tools/client/ryzom_installer/src/filesextractor.cpp
@@ -15,7 +15,8 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include "stdpch.h"
-#include "archive.h"
+#include "filesextractor.h"
+#include "operation.h"
 #include "utils.h"
 
 #include "nel/misc/big_file.h"
@@ -32,10 +33,6 @@
 
 #include <sys/stat.h>
 
-#ifdef Q_OS_WIN
-#include <Windows.h>
-#endif
-
 #ifndef FILE_ATTRIBUTE_READONLY
 #define FILE_ATTRIBUTE_READONLY            0x1
 #endif
@@ -244,20 +241,31 @@ public:
 	}
 };
 
-CArchive::CArchive(QObject *parent):QObject(parent), m_mustStop(false)
+CFilesExtractor::CFilesExtractor(IOperationProgressListener *listener):m_listener(listener)
 {
 }
 
-CArchive::~CArchive()
+CFilesExtractor::~CFilesExtractor()
 {
 }
 
-bool CArchive::extract(const QString &filename, const QString &dest)
+void CFilesExtractor::setSourceFile(const QString &src)
 {
-	m_filename = filename;
-	m_dest = dest;
+	m_sourceFile = src;
+}
 
-	QFile file(m_filename);
+void CFilesExtractor::setDesinationDirectory(const QString &dst)
+{
+	m_destinationDirectory = dst;
+}
+
+bool CFilesExtractor::exec()
+{
+	if (m_sourceFile.isEmpty() || m_destinationDirectory.isEmpty()) return false;
+
+	if (m_listener) m_listener->operationPrepare();
+
+	QFile file(m_sourceFile);
 
 	// open archive file to check format
 	if (!file.open(QFile::ReadOnly)) return false;
@@ -270,253 +278,38 @@ bool CArchive::extract(const QString &filename, const QString &dest)
 
 	// create destination directory
 	QDir dir;
-	dir.mkpath(dest);
-
-	QFuture<bool> future;
+	dir.mkpath(m_destinationDirectory);
 
 	// compare to supported formats and call the appropriate decompressor
 	if (header == "7z")
 	{
-		future = QtConcurrent::run(this, &CArchive::extract7z);
-	}
-	else if (header == "PK")
-	{
-		future = QtConcurrent::run(this, &CArchive::extractZip);
-	}
-	else if (QFileInfo(filename).suffix().toLower() == "bnp")
-	{
-		future = QtConcurrent::run(this, &CArchive::extractBnp);
-	}
-	else
-	{
-		qDebug() << "Unsupported format";
-		return false;
+		return extract7z();
 	}
 
-	return true;
+	if (header == "PK")
+	{
+		return extractZip();
+	}
+
+	if (QFileInfo(m_sourceFile).suffix().toLower() == "bnp")
+	{
+		return extractBnp();
+	}
+
+	qDebug() << "Unsupported format";
+	return false;
 }
 
-void CArchive::getFilesList(const QString &srcDir, const QString &dstDir, const QStringList &filter, FilesToCopy &files)
+bool CFilesExtractor::extract7z()
 {
-	QDir dir(srcDir);
-
-	QFileInfoList entries = dir.entryInfoList(filter);
-
-	foreach(const QFileInfo &entry, entries)
-	{
-		QString fullPath = entry.absoluteFilePath();
-
-		QString dstPath = dstDir + "/" + dir.relativeFilePath(fullPath);
-
-		if (entry.isDir())
-		{
-			QDir().mkpath(dstPath);
-
-			QDir subDir(fullPath);
-
-			QDirIterator it(subDir, QDirIterator::Subdirectories);
-
-			while (it.hasNext())
-			{
-				fullPath = it.next();
-
-				if (it.fileName().startsWith('.')) continue;
-
-				QFileInfo fileInfo = it.fileInfo();
-
-				dstPath = dstDir + "/" + dir.relativeFilePath(fullPath);
-
-				if (fileInfo.isDir())
-				{
-					QDir().mkpath(dstPath);
-				}
-				else
-				{
-					FileToCopy file;
-					file.filename = it.fileName();
-					file.src = it.filePath();
-					file.dst = dstPath;
-					file.size = it.fileInfo().size();
-					file.date = it.fileInfo().lastModified();
-
-					files << file;
-				}
-			}
-		}
-		else
-		{
-			FileToCopy file;
-			file.filename = entry.fileName();
-			file.src = entry.filePath();
-			file.dst = dstPath;
-			file.size = entry.size();
-			file.date = entry.lastModified();
-
-			files << file;
-		}
-	}
-}
-
-bool CArchive::copyServerFiles()
-{
-	emit extractPrepare();
-
-	FilesToCopy files;
-
-	QStringList serverFiles;
-	serverFiles << "cfg";
-	serverFiles << "data";
-	serverFiles << "examples";
-	serverFiles << "patch";
-	serverFiles << "unpack";
-	serverFiles << "client_default.cfg";
-
-	CArchive::getFilesList(m_filename, m_dest, serverFiles, files);
-
-	return copyFiles(files);
-}
-
-bool CArchive::copyProfileFiles()
-{
-	emit extractPrepare();
-
-	FilesToCopy files;
-
-	QStringList configFiles;
-	configFiles << "cache";
-	configFiles << "save";
-	configFiles << "user";
-	configFiles << "screenshots";
-	configFiles << "client.cfg";
-	configFiles << "*.log";
-
-	CArchive::getFilesList(m_filename, m_dest, configFiles, files);
-
-	return copyFiles(files);
-}
-
-bool CArchive::cleanServerFiles(const QString &directory)
-{
-	QDir dir(directory);
-
-	// directory doesn't exist
-	if (!dir.exists()) return false;
-
-	if (!dir.cd("data") && dir.exists()) return false;
-
-	// temporary files
-	QStringList files = dir.entryList(QStringList() << "*.string_cache" << "*.packed_sheets" << "*.packed" << "*.pem", QDir::Files);
-
-	foreach(const QString &file, files)
-	{
-		dir.remove(file);
-	}
-
-	// fonts directory is not needed anymore
-	if (dir.cd("fonts") && dir.exists())
-	{
-		dir.removeRecursively();
-	}
-
-	emit done();
-
-	return true;
-}
-
-bool CArchive::copyServerFiles(const QString &src, const QString &dst)
-{
-	if (src.isEmpty() || dst.isEmpty()) return false;
-
-	m_filename = src;
-	m_dest = dst;
-
-	// create destination directory
-	QDir().mkpath(dst);
-
-	QFuture<bool> future = QtConcurrent::run(this, &CArchive::copyServerFiles);
-
-	return true;
-}
-
-bool CArchive::copyProfileFiles(const QString &src, const QString &dst)
-{
-	if (src.isEmpty() || dst.isEmpty()) return false;
-
-	m_filename = src;
-	m_dest = dst;
-
-	// create destination directory
-	QDir().mkpath(dst);
-
-	QFuture<bool> future = QtConcurrent::run(this, &CArchive::copyProfileFiles);
-
-	return true;
-}
-
-bool CArchive::copyFiles(const FilesToCopy &files)
-{
-	qint64 totalSize = 0;
-
-	foreach(const FileToCopy &file, files)
-	{
-		totalSize += file.size;
-
-		qDebug() << file.filename;
-	}
-
-	emit extractInit(0, totalSize);
-
-	emit extractStart();
-
-	qint64 processedSize = 0;
-
-	foreach(const FileToCopy &file, files)
-	{
-		if (mustStop())
-		{
-			emit extractStop();
-			return true;
-		}
-
-		emit extractProgress(processedSize, file.filename);
-
-		QFileInfo dstFileInfo(file.dst);
-
-		if (dstFileInfo.size() != file.size || dstFileInfo.lastModified() != file.date)
-		{
-			if (!QFile::copy(file.src, file.dst))
-			{
-				emit extractFail(tr("Unable to copy file %1").arg(file.src));
-				return false;
-			}
-
-			if (!NLMISC::CFile::setFileModificationDate(qToUtf8(file.dst), file.date.toTime_t()))
-			{
-				qDebug() << "Unable to change date";
-			}
-		}
-
-		processedSize += file.size;
-	}
-
-	emit extractSuccess(totalSize);
-	emit done();
-
-	return true;
-}
-
-bool CArchive::extract7z()
-{
-	Q7zFile inFile(m_filename);
+	Q7zFile inFile(m_sourceFile);
 
 	if (!inFile.open())
 	{
-		emit extractFail(tr("Unable to open %1").arg(m_filename));
+		if (m_listener) m_listener->operationFail(QApplication::tr("Unable to open %1").arg(m_sourceFile));
 		return false;
 	}
 
-	emit extractPrepare();
-
 	UInt16 *temp = NULL;
 	size_t tempSize = 0;
 
@@ -558,9 +351,11 @@ bool CArchive::extract7z()
 			if (!isDir) total += SzArEx_GetFileSize(&db, i);
 		}
 
-		emit extractInit(0, total);
-
-		emit extractStart();
+		if (m_listener)
+		{
+			m_listener->operationInit(0, total);
+			m_listener->operationStart();
+		}
 
 		// variables used for decompression
 		UInt32 blockIndex = 0xFFFFFFFF;
@@ -570,7 +365,7 @@ bool CArchive::extract7z()
 		// process each file in archive
 		for (UInt32 i = 0; i < db.NumFiles; ++i)
 		{
-			if (mustStop())
+			if (m_listener && m_listener->operationShouldStop())
 			{
 				res = SZ_ERROR_INTERRUPTED;
 				break;
@@ -602,7 +397,7 @@ bool CArchive::extract7z()
 
 			if (!isDir)
 			{
-				emit extractProgress(totalUncompressed, filename);
+				if (m_listener) m_listener->operationProgress(totalUncompressed, filename);
 
 				res = SzArEx_Extract(&db, &lookStream.s, i, &blockIndex, &outBuffer, &outBufferSize,
 					&offset, &outSizeProcessed, &allocImp, &allocTempImp);
@@ -610,7 +405,7 @@ bool CArchive::extract7z()
 				if (res != SZ_OK) break;
 			}
 
-			QString destPath = m_dest + '/' + path;
+			QString destPath = m_destinationDirectory + '/' + path;
 
 			QDir dir;
 
@@ -626,7 +421,7 @@ bool CArchive::extract7z()
 
 			if (!outFile.open(QFile::WriteOnly))
 			{
-				error = tr("Unable to open output file");
+				error = QApplication::tr("Unable to open output file");
 				res = SZ_ERROR_FAIL;
 				break;
 			}
@@ -635,7 +430,7 @@ bool CArchive::extract7z()
 
 			if (processedSize != outSizeProcessed)
 			{
-				error = tr("Unable to write output file");
+				error = QApplication::tr("Unable to write output file");
 				res = SZ_ERROR_FAIL;
 				break;
 			}
@@ -644,7 +439,7 @@ bool CArchive::extract7z()
 
 			totalUncompressed += SzArEx_GetFileSize(&db, i);
 
-			emit extractProgress(totalUncompressed, filename);
+			if (m_listener) m_listener->operationProgress(totalUncompressed, filename);
 
 			if (SzBitWithVals_Check(&db.Attribs, i))
 				Set7zFileAttrib(destPath, db.Attribs.Vals[i]);
@@ -659,24 +454,27 @@ bool CArchive::extract7z()
 	switch(res)
 	{
 		case SZ_OK:
-		emit extractSuccess(totalUncompressed);
-		emit done();
+		if (m_listener)
+		{
+			m_listener->operationSuccess(totalUncompressed);
+			m_listener->operationFinish();
+		}
 		return true;
 
 		case SZ_ERROR_INTERRUPTED:
-		emit extractStop();
+		if (m_listener) m_listener->operationStop();
 		return true;
 
 		case SZ_ERROR_UNSUPPORTED:
-		error = tr("7zip decoder doesn't support this archive");
+		error = QApplication::tr("7zip decoder doesn't support this archive");
 		break;
 
 		case SZ_ERROR_MEM:
-		error = tr("Unable to allocate memory");
+		error = QApplication::tr("Unable to allocate memory");
 		break;
 
 		case SZ_ERROR_CRC:
-		error = tr("7zip decoder doesn't support this archive");
+		error = QApplication::tr("7zip decoder doesn't support this archive");
 		break;
 
 		case SZ_ERROR_FAIL:
@@ -684,21 +482,19 @@ bool CArchive::extract7z()
 		break;
 
 		default:
-		error = tr("Error %1").arg(res);
+		error = QApplication::tr("Error %1").arg(res);
 	}
 
-	emit extractFail(error);
+	if (m_listener) m_listener->operationFail(error);
 
 	return false;
 }
 
-bool CArchive::extractZip()
+bool CFilesExtractor::extractZip()
 {
-	emit extractPrepare();
+	QZipReader reader(m_sourceFile);
 
-	QZipReader reader(m_filename);
-
-	QDir baseDir(m_dest);
+	QDir baseDir(m_destinationDirectory);
 
 	// create directories first
 	QList<QZipReader::FileInfo> allFiles = reader.fileInfoList();
@@ -709,17 +505,17 @@ bool CArchive::extractZip()
 	{
 		if (fi.isDir)
 		{
-			const QString absPath = m_dest + QDir::separator() + fi.filePath;
+			const QString absPath = m_destinationDirectory + QDir::separator() + fi.filePath;
 
 			if (!baseDir.mkpath(fi.filePath))
 			{
-				emit extractFail(tr("Unable to create directory %1").arg(absPath));
+				if (m_listener) m_listener->operationFail(QApplication::tr("Unable to create directory %1").arg(absPath));
 				return false;
 			}
 
 			if (!QFile::setPermissions(absPath, fi.permissions))
 			{
-				emit extractFail(tr("Unable to set permissions of %1").arg(absPath));
+				if (m_listener) m_listener->operationFail(QApplication::tr("Unable to set permissions of %1").arg(absPath));
 				return false;
 			}
 		}
@@ -727,20 +523,23 @@ bool CArchive::extractZip()
 		totalSize += fi.size;
 	}
 
-	emit extractInit(0, totalSize);
-	emit extractStart();
+	if (m_listener)
+	{
+		m_listener->operationInit(0, totalSize);
+		m_listener->operationStart();
+	}
 
 	// client won't use symbolic links so don't process them
 
 	foreach (const QZipReader::FileInfo &fi, allFiles)
 	{
-		const QString absPath = m_dest + QDir::separator() + fi.filePath;
+		const QString absPath = m_destinationDirectory + QDir::separator() + fi.filePath;
 
 		if (fi.isFile)
 		{
-			if (mustStop())
+			if (m_listener && m_listener->operationShouldStop())
 			{
-				emit extractStop();
+				m_listener->operationStop();
 				return true;
 			}
 
@@ -748,7 +547,7 @@ bool CArchive::extractZip()
 
 			if (!f.open(QIODevice::WriteOnly))
 			{
-				emit extractFail(tr("Unable to open %1").arg(absPath));
+				if (m_listener) m_listener->operationFail(QApplication::tr("Unable to open %1").arg(absPath));
 				return false;
 			}
 
@@ -757,58 +556,65 @@ bool CArchive::extractZip()
 			f.setPermissions(fi.permissions);
 			f.close();
 
-			emit extractProgress(currentSize, QFileInfo(absPath).fileName());
+			if (m_listener) m_listener->operationProgress(currentSize, QFileInfo(absPath).fileName());
 		}
 	}
 
-	emit extractSuccess(totalSize);
-	emit done();
+	if (m_listener)
+	{
+		m_listener->operationSuccess(totalSize);
+		m_listener->operationFinish();
+	}
 
 	return true;
 }
 
-bool CArchive::progress(const std::string &filename, uint32 currentSize, uint32 totalSize)
+bool CFilesExtractor::progress(const std::string &filename, uint32 currentSize, uint32 totalSize)
 {
-	if (mustStop())
+	if (m_listener && m_listener->operationShouldStop())
 	{
-		emit extractStop();
+		m_listener->operationStop();
 	
 		return false;
 	}
 
 	if (currentSize == 0)
 	{
-		emit extractInit(0, (qint64)totalSize);
-		emit extractStart();
+		if (m_listener)
+		{
+			m_listener->operationInit(0, (qint64)totalSize);
+			m_listener->operationStart();
+		}
 	}
 
-	emit extractProgress((qint64)currentSize, qFromUtf8(filename));
+	if (m_listener) m_listener->operationProgress((qint64)currentSize, qFromUtf8(filename));
 	
 	if (currentSize == totalSize)
 	{
-		emit extractSuccess((qint64)totalSize);
-		emit done();
+		if (m_listener)
+		{
+			m_listener->operationSuccess((qint64)totalSize);
+			m_listener->operationFinish();
+		}
 	}
 
 	return true;
 }
 
-bool CArchive::extractBnp()
+bool CFilesExtractor::extractBnp()
 {
 	QString error;
 
-	emit extractPrepare();
-
-	NLMISC::CBigFile::TUnpackProgressCallback cbMethod = NLMISC::CBigFile::TUnpackProgressCallback(this, &CArchive::progress);
+	NLMISC::CBigFile::TUnpackProgressCallback cbMethod = NLMISC::CBigFile::TUnpackProgressCallback(this, &CFilesExtractor::progress);
 
 	try
 	{
-		if (NLMISC::CBigFile::unpack(qToUtf8(m_filename), qToUtf8(m_dest), &cbMethod))
+		if (NLMISC::CBigFile::unpack(qToUtf8(m_sourceFile), qToUtf8(m_destinationDirectory), &cbMethod))
 		{
 			return true;
 		}
 
-		if (mustStop())
+		if (m_listener && m_listener->operationShouldStop())
 		{
 			// stopped
 
@@ -819,37 +625,22 @@ bool CArchive::extractBnp()
 	}
 	catch(const NLMISC::EDiskFullError &e)
 	{
-		error = tr("disk full");
+		error = QApplication::tr("disk full");
 	}
 	catch(const NLMISC::EWriteError &e)
 	{
-		error = tr("unable to write %1").arg(qFromUtf8(e.Filename));
+		error = QApplication::tr("unable to write %1").arg(qFromUtf8(e.Filename));
 	}
 	catch(const NLMISC::EReadError &e)
 	{
-		error = tr("unable to read %1").arg(qFromUtf8(e.Filename));
+		error = QApplication::tr("unable to read %1").arg(qFromUtf8(e.Filename));
 	}
 	catch(const std::exception &e)
 	{
-		error = tr("failed (%1)").arg(qFromUtf8(e.what()));
+		error = QApplication::tr("failed (%1)").arg(qFromUtf8(e.what()));
 	}
 
-	emit extractFail(tr("Unable to unpack %1 to %2: %3").arg(m_filename).arg(m_dest).arg(error));
+	if (m_listener) m_listener->operationFail(QApplication::tr("Unable to unpack %1 to %2: %3").arg(m_sourceFile).arg(m_destinationDirectory).arg(error));
 
 	return false;
 }
-
-void CArchive::stop()
-{
-	QMutexLocker locker(&m_mutex);
-
-	m_mustStop = true;
-}
-
-bool CArchive::mustStop()
-{
-	QMutexLocker locker(&m_mutex);
-
-	return m_mustStop;
-}
-
diff --git a/code/ryzom/tools/client/ryzom_installer/src/filesextractor.h b/code/ryzom/tools/client/ryzom_installer/src/filesextractor.h
new file mode 100644
index 000000000..276e4a847
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/filesextractor.h
@@ -0,0 +1,53 @@
+// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
+// Copyright (C) 2010  Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef FILEXTRACTOR_H
+#define FILEXTRACTOR_H
+
+class IOperationProgressListener;
+
+/**
+ * Files extractor
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CFilesExtractor
+{
+public:
+	CFilesExtractor(IOperationProgressListener *listener);
+	virtual ~CFilesExtractor();
+
+	void setSourceFile(const QString &src);
+	void setDesinationDirectory(const QString &src);
+
+	bool exec();
+
+protected:
+
+	bool extract7z();
+	bool extractZip();
+	bool extractBnp();
+
+	bool progress(const std::string &filename, uint32 currentFile, uint32 totalFiles);
+
+	IOperationProgressListener *m_listener;
+
+	QString m_sourceFile;
+	QString m_destinationDirectory;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/operation.h b/code/ryzom/tools/client/ryzom_installer/src/operation.h
new file mode 100644
index 000000000..b8cfeea34
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/operation.h
@@ -0,0 +1,37 @@
+// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
+// Copyright (C) 2010  Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef OPERATION_H
+#define OPERATION_H
+
+class IOperationProgressListener
+{
+public:
+	virtual ~IOperationProgressListener() {}
+
+	virtual void operationPrepare() =0;
+	virtual void operationInit(qint64 current, qint64 total) =0;
+	virtual void operationStart() =0;
+	virtual void operationStop() =0;
+	virtual void operationProgress(qint64 current, const QString &filename) =0;
+	virtual void operationSuccess(qint64 total) =0;
+	virtual void operationFail(const QString &error) =0;
+	virtual void operationFinish() =0;
+
+	virtual bool operationShouldStop() =0;
+};
+
+#endif