Merge pull request #1103 from ONLYOFFICE/feature/progress_unzipping_updates

Add feature: progress unzipping updates
This commit is contained in:
Maxim Kadushkin
2024-01-11 11:16:36 +03:00
committed by GitHub
8 changed files with 270 additions and 150 deletions

View File

@ -66,6 +66,7 @@ enum MsgCommands {
MSG_StartReplacingFiles,
MSG_ClearTempFiles,
MSG_Progress,
MSG_UnzipProgress,
MSG_StopDownload,
MSG_OtherError,
MSG_SetLanguage,

View File

@ -224,6 +224,9 @@ void CSvcManager::init()
m_pUnzip->onComplete([=](int error) {
onCompleteUnzip(error);
});
m_pUnzip->onProgress([=](int percent) {
m_socket->sendMessage(MSG_UnzipProgress, to_tstring(percent));
});
m_socket->onMessageReceived([=](void *data, size_t) {
vector<tstring> params;
if (m_socket->parseMessage(data, params) == 3) {

View File

@ -34,101 +34,135 @@
#include "platform_linux/utils.h"
#include <archive.h>
#include <archive_entry.h>
#include <sys/stat.h>
#include <cstring>
#define BLOCK_SIZE 10240
int unzipArchive(const string &zipFilePath, const string &folderPath, std::atomic_bool &run, string &error)
class CUnzip::CUnzipPrivate
{
if (!NS_File::fileExists(zipFilePath) || !NS_File::dirExists(folderPath)) {
error = "Archive path is empty or dest dir not exist";
return UNZIP_ERROR;
}
public:
CUnzipPrivate()
{}
~CUnzipPrivate()
{}
struct archive *arch = archive_read_new();
archive_read_support_filter_xz(arch);
archive_read_support_format_tar(arch);
if (archive_read_open_filename(arch, zipFilePath.c_str(), BLOCK_SIZE) != ARCHIVE_OK) {
error = "Cannot open archive";
archive_read_free(arch);
return UNZIP_ERROR;
}
int unzipArchive(const string &zipFilePath, const string &folderPath, string &error)
{
if (!NS_File::fileExists(zipFilePath) || !NS_File::dirExists(folderPath)) {
error = "Archive path is empty or dest dir not exist";
return UNZIP_ERROR;
}
int prev_percent = -1;
struct stat file_stat;
long total_size = (stat(zipFilePath.c_str(), &file_stat) == 0) ? file_stat.st_size : 0;
int res = ARCHIVE_OK;
int ex_code = UNZIP_OK;
struct archive_entry *entry;
while ((res = archive_read_next_header(arch, &entry)) == ARCHIVE_OK) {
if (!run) {
ex_code = UNZIP_ABORT;
break;
struct archive *arch = archive_read_new();
archive_read_support_filter_xz(arch);
archive_read_support_format_tar(arch);
if (archive_read_open_filename(arch, zipFilePath.c_str(), BLOCK_SIZE) != ARCHIVE_OK) {
error = "Cannot open archive";
archive_read_free(arch);
return UNZIP_ERROR;
}
const char *entryname = archive_entry_pathname(entry);
if (!entryname) {
error = "Invalid entry name";
break;
}
char outpath[1024] = {0};
snprintf(outpath, sizeof(outpath), "%s/%s", folderPath.c_str(), entryname);
if (archive_entry_filetype(entry) == AE_IFREG) {
archive_entry_set_pathname(entry, outpath);
res = archive_read_extract(arch, entry, ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM
| ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS
| ARCHIVE_EXTRACT_NO_OVERWRITE);
if (res != ARCHIVE_OK) {
error = "Cannot extract entry";
int res = ARCHIVE_OK;
int ex_code = UNZIP_OK;
struct archive_entry *entry;
while ((res = archive_read_next_header(arch, &entry)) == ARCHIVE_OK) {
if (!run) {
ex_code = UNZIP_ABORT;
break;
}
const char *entryname = archive_entry_pathname(entry);
if (!entryname) {
error = "Invalid entry name";
break;
}
char outpath[1024] = {0};
snprintf(outpath, sizeof(outpath), "%s/%s", folderPath.c_str(), entryname);
if (archive_entry_filetype(entry) == AE_IFREG) {
archive_entry_set_pathname(entry, outpath);
res = archive_read_extract(arch, entry, ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM
| ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS
| ARCHIVE_EXTRACT_NO_OVERWRITE);
if (res != ARCHIVE_OK) {
error = "Cannot extract entry";
break;
}
if (total_size > 0 && progress_callback) {
int percent = static_cast<int>(100.0 * ((double)archive_filter_bytes(arch, -1)/total_size));
if (percent != prev_percent) {
progress_callback(percent);
prev_percent = percent;
}
}
}
}
if (res != ARCHIVE_EOF && ex_code != UNZIP_ABORT) {
error = string("Error reading archive: ") + archive_error_string(arch);
ex_code = UNZIP_ERROR;
}
archive_read_close(arch);
archive_read_free(arch);
return ex_code;
}
if (res != ARCHIVE_EOF && ex_code != UNZIP_ABORT) {
error = string("Error reading archive: ") + archive_error_string(arch);
ex_code = UNZIP_ERROR;
}
FnVoidInt complete_callback = nullptr,
progress_callback = nullptr;
std::atomic_bool run;
std::future<void> future;
};
archive_read_close(arch);
archive_read_free(arch);
return ex_code;
}
CUnzip::CUnzip()
CUnzip::CUnzip() :
pimpl(new CUnzipPrivate)
{
m_run = false;
pimpl->run = false;
}
CUnzip::~CUnzip()
{
m_run = false;
if (m_future.valid())
m_future.wait();
pimpl->run = false;
if (pimpl->future.valid())
pimpl->future.wait();
delete pimpl, pimpl = nullptr;
}
void CUnzip::extractArchive(const string &zipFilePath, const string &folderPath)
{
m_run = false;
if (m_future.valid())
m_future.wait();
m_run = true;
m_future = std::async(std::launch::async, [=]() {
pimpl->run = false;
if (pimpl->future.valid())
pimpl->future.wait();
pimpl->run = true;
pimpl->future = std::async(std::launch::async, [=]() {
string error;
int res = unzipArchive(zipFilePath, folderPath, m_run, error);
int res = pimpl->unzipArchive(zipFilePath, folderPath, error);
if (!error.empty())
fprintf(stderr, "%s", error.c_str());
if (m_complete_callback)
m_complete_callback(res);
if (pimpl->complete_callback)
pimpl->complete_callback(res);
});
}
void CUnzip::stop()
{
m_run = false;
pimpl->run = false;
}
void CUnzip::onComplete(FnVoidInt callback)
{
m_complete_callback = callback;
pimpl->complete_callback = callback;
}
void CUnzip::onProgress(FnVoidInt callback)
{
pimpl->progress_callback = callback;
}

View File

@ -57,11 +57,11 @@ public:
/* callback */
void onComplete(FnVoidInt callback);
void onProgress(FnVoidInt callback);
private:
FnVoidInt m_complete_callback = nullptr;
std::atomic_bool m_run;
std::future<void> m_future;
class CUnzipPrivate;
CUnzipPrivate *pimpl = nullptr;
};
#endif // CUNZIP_H

View File

@ -36,123 +36,194 @@
#include <Shldisp.h>
int extractRecursively(IShellDispatch *pISD, const CComPtr<Folder> &pSrcFolder, const wstring &destFolder, std::atomic_bool &run)
class CUnzip::CUnzipPrivate
{
CComPtr<FolderItems> pItems;
if (FAILED(pSrcFolder->Items(&pItems)))
return UNZIP_ERROR;
public:
CUnzipPrivate()
{}
~CUnzipPrivate()
{}
long itemCount = 0;
if (FAILED(pItems->get_Count(&itemCount)))
return UNZIP_ERROR;
bool calcFilesCountRecursively(IShellDispatch *pISD, const CComPtr<Folder> &pSrcFolder)
{
CComPtr<FolderItems> pItems;
if (FAILED(pSrcFolder->Items(&pItems)))
return false;
for (int i = 0; i < itemCount; i++) {
if (!run)
return UNZIP_ABORT;
long itemCount = 0;
if (FAILED(pItems->get_Count(&itemCount)))
return false;
CComPtr<FolderItem> pItem;
if (FAILED(pItems->Item(CComVariant(i), &pItem)))
return UNZIP_ERROR;
for (int i = 0; i < itemCount; i++) {
CComPtr<FolderItem> pItem;
if (FAILED(pItems->Item(CComVariant(i), &pItem)))
return false;
VARIANT_BOOL isFolder = VARIANT_FALSE;
if (FAILED(pItem->get_IsFolder(&isFolder)))
return UNZIP_ERROR;
VARIANT_BOOL isFolder = VARIANT_FALSE;
if (FAILED(pItem->get_IsFolder(&isFolder)))
return false;
if (isFolder == VARIANT_TRUE) {
// Source path
CComPtr<Folder> pSubFolder;
if (FAILED(pISD->NameSpace(CComVariant(pItem), &pSubFolder)))
return UNZIP_ERROR;
if (isFolder == VARIANT_TRUE) {
CComPtr<Folder> pSubFolder;
if (FAILED(pISD->NameSpace(CComVariant(pItem), &pSubFolder)))
return false;
// Dest path
BSTR bstrName;
if (FAILED(pItem->get_Name(&bstrName)))
return UNZIP_ERROR;
if (!calcFilesCountRecursively(pISD, pSubFolder))
return false;
wstring targetFolder = destFolder + L"\\" + bstrName;
SysFreeString(bstrName);
if (CreateDirectory(targetFolder.c_str(), NULL) == 0)
return UNZIP_ERROR;
int res = extractRecursively(pISD, pSubFolder, targetFolder, run);
if (res != UNZIP_OK)
return res;
} else {
CComPtr<Folder> pDestFolder;
if (FAILED(pISD->NameSpace(CComVariant(destFolder.c_str()), &pDestFolder)))
return UNZIP_ERROR;
if (FAILED(pDestFolder->CopyHere(CComVariant(pItem), CComVariant(1024 | 512 | 16 | 4))))
return UNZIP_ERROR;
} else {
++total_count;
}
}
}
return UNZIP_OK;
}
int unzipArchive(const wstring &zipFilePath, const wstring &folderPath, std::atomic_bool &run)
{
if (!NS_File::fileExists(zipFilePath) || !NS_File::dirExists(folderPath))
return UNZIP_ERROR;
wstring file = NS_File::toNativeSeparators(zipFilePath);
wstring path = NS_File::toNativeSeparators(folderPath);
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
return UNZIP_ERROR;
IShellDispatch *pShell = NULL;
hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShell));
if (FAILED(hr)) {
CoUninitialize();
return UNZIP_ERROR;
return true;
}
CComPtr<Folder> pSrcFolder;
if (FAILED(pShell->NameSpace(CComVariant(file.c_str()), &pSrcFolder))) {
int extractRecursively(IShellDispatch *pISD, const CComPtr<Folder> &pSrcFolder, const wstring &destFolder)
{
CComPtr<FolderItems> pItems;
if (FAILED(pSrcFolder->Items(&pItems)))
return UNZIP_ERROR;
long itemCount = 0;
if (FAILED(pItems->get_Count(&itemCount)))
return UNZIP_ERROR;
for (int i = 0; i < itemCount; i++) {
if (!run)
return UNZIP_ABORT;
CComPtr<FolderItem> pItem;
if (FAILED(pItems->Item(CComVariant(i), &pItem)))
return UNZIP_ERROR;
VARIANT_BOOL isFolder = VARIANT_FALSE;
if (FAILED(pItem->get_IsFolder(&isFolder)))
return UNZIP_ERROR;
if (isFolder == VARIANT_TRUE) {
// Source path
CComPtr<Folder> pSubFolder;
if (FAILED(pISD->NameSpace(CComVariant(pItem), &pSubFolder)))
return UNZIP_ERROR;
// Dest path
BSTR bstrName;
if (FAILED(pItem->get_Name(&bstrName)))
return UNZIP_ERROR;
wstring targetFolder = destFolder + L"\\" + bstrName;
SysFreeString(bstrName);
if (CreateDirectory(targetFolder.c_str(), NULL) == 0)
return UNZIP_ERROR;
int res = extractRecursively(pISD, pSubFolder, targetFolder);
if (res != UNZIP_OK)
return res;
} else {
CComPtr<Folder> pDestFolder;
if (FAILED(pISD->NameSpace(CComVariant(destFolder.c_str()), &pDestFolder)))
return UNZIP_ERROR;
if (FAILED(pDestFolder->CopyHere(CComVariant(pItem), CComVariant(1024 | 512 | 16 | 4))))
return UNZIP_ERROR;
if (total_count > 0 && progress_callback) {
++curr_count;
int percent = static_cast<int>(100.0 * ((double)curr_count / total_count));
if (percent != prev_percent) {
progress_callback(percent);
prev_percent = percent;
}
}
}
}
return UNZIP_OK;
}
int unzipArchive(const wstring &zipFilePath, const wstring &folderPath)
{
if (!NS_File::fileExists(zipFilePath) || !NS_File::dirExists(folderPath))
return UNZIP_ERROR;
wstring file = NS_File::toNativeSeparators(zipFilePath);
wstring path = NS_File::toNativeSeparators(folderPath);
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
return UNZIP_ERROR;
IShellDispatch *pShell = NULL;
hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShell));
if (FAILED(hr)) {
CoUninitialize();
return UNZIP_ERROR;
}
CComPtr<Folder> pSrcFolder;
if (FAILED(pShell->NameSpace(CComVariant(file.c_str()), &pSrcFolder))) {
pShell->Release();
CoUninitialize();
return UNZIP_ERROR;
}
prev_percent = -1;
curr_count = 0;
total_count = 0;
if (!calcFilesCountRecursively(pShell, pSrcFolder))
total_count = 0;
int res = extractRecursively(pShell, pSrcFolder, path);
pSrcFolder.Release();
pShell->Release();
CoUninitialize();
return UNZIP_ERROR;
return res;
}
int res = extractRecursively(pShell, pSrcFolder, path, run);
pSrcFolder.Release();
pShell->Release();
CoUninitialize();
return res;
}
FnVoidInt complete_callback = nullptr,
progress_callback = nullptr;
std::atomic_bool run;
std::future<void> future;
int curr_count = 0,
total_count = 0,
prev_percent = -1;
};
CUnzip::CUnzip()
CUnzip::CUnzip() :
pimpl(new CUnzipPrivate)
{
m_run = false;
pimpl->run = false;
}
CUnzip::~CUnzip()
{
m_run = false;
if (m_future.valid())
m_future.wait();
pimpl->run = false;
if (pimpl->future.valid())
pimpl->future.wait();
delete pimpl, pimpl = nullptr;
}
void CUnzip::extractArchive(const wstring &zipFilePath, const wstring &folderPath)
{
m_run = false;
if (m_future.valid())
m_future.wait();
m_run = true;
m_future = std::async(std::launch::async, [=]() {
int res = unzipArchive(zipFilePath, folderPath, m_run);
if (m_complete_callback)
m_complete_callback(res);
pimpl->run = false;
if (pimpl->future.valid())
pimpl->future.wait();
pimpl->run = true;
pimpl->future = std::async(std::launch::async, [=]() {
int res = pimpl->unzipArchive(zipFilePath, folderPath);
if (pimpl->complete_callback)
pimpl->complete_callback(res);
});
}
void CUnzip::stop()
{
m_run = false;
pimpl->run = false;
}
void CUnzip::onComplete(FnVoidInt callback)
{
m_complete_callback = callback;
pimpl->complete_callback = callback;
}
void CUnzip::onProgress(FnVoidInt callback)
{
pimpl->progress_callback = callback;
}

View File

@ -57,11 +57,11 @@ public:
/* callback */
void onComplete(FnVoidInt callback);
void onProgress(FnVoidInt callback);
private:
FnVoidInt m_complete_callback = nullptr;
std::atomic_bool m_run;
std::future<void> m_future;
class CUnzipPrivate;
CUnzipPrivate *pimpl = nullptr;
};
#endif // CUNZIP_H

View File

@ -91,6 +91,7 @@ const char *SVC_TXT_ERR_UNPACKING = QT_TRANSLATE_NOOP("CUpdateManager", "An er
*TXT_AVAILABLE_UPD = QT_TRANSLATE_NOOP("CUpdateManager", "Update is available (version %1)"),
*TXT_DOWNLOADING_UPD = QT_TRANSLATE_NOOP("CUpdateManager", "Downloading new version %1 (%2%)"),
*TXT_PREPARING_UPD = QT_TRANSLATE_NOOP("CUpdateManager", "Preparing update..."),
*TXT_UNZIP_UPD = QT_TRANSLATE_NOOP("CUpdateManager", "Preparing update (%1%)"),
*TXT_RESTART_TO_UPD = QT_TRANSLATE_NOOP("CUpdateManager", "To finish updating, restart app"),
*TXT_ERR_NOT_ALLOWED = QT_TRANSLATE_NOOP("CUpdateManager", "Updates are not allowed!"),
*TXT_ERR_URL = QT_TRANSLATE_NOOP("CUpdateManager", "Unable to check update: URL not defined."),
@ -351,6 +352,10 @@ void CUpdateManager::init()
QMetaObject::invokeMethod(this, "onProgressSlot", Qt::QueuedConnection, Q_ARG(int, std::stoi(params[1])));
break;
case MSG_UnzipProgress:
QMetaObject::invokeMethod(this, "onUnzipProgressSlot", Qt::QueuedConnection, Q_ARG(int, std::stoi(params[1])));
break;
case MSG_RequestContentLenght: {
double fileSize = std::stod(params[1])/1024/1024;
m_packageData->fileSize = (fileSize == 0) ? "--" : QString::number(fileSize, 'f', 1);
@ -450,6 +455,11 @@ void CUpdateManager::onProgressSlot(const int percent)
refreshStartPage({"", {TXT_DOWNLOADING_UPD, m_packageData->version, QString::number(percent)}});
}
void CUpdateManager::onUnzipProgressSlot(const int percent)
{
refreshStartPage({"", {TXT_UNZIP_UPD, QString::number(percent)}});
}
void CUpdateManager::onError(const QString &error)
{
const char *_error = SVC_TXT_ERR_OTHER;

View File

@ -141,6 +141,7 @@ private slots:
void onLoadUpdateFinished(const QString &filePath);
void showStartInstallMessage(QWidget *parent);
void onProgressSlot(const int percent);
void onUnzipProgressSlot(const int percent);
void onError(const QString &error);
void criticalMsg(QWidget *parent, const QString &msg);
};