diff --git a/backup-daemon/backup-daemon.pro b/backup-daemon/backup-daemon.pro index 662dc37..88d7f0f 100755 --- a/backup-daemon/backup-daemon.pro +++ b/backup-daemon/backup-daemon.pro @@ -20,6 +20,7 @@ DEFINES += QT_DEPRECATED_WARNINGS # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 +QMAKE_CXXFLAGS += -Wno-implicit-fallthrough HEADERS += \ ../common/mydefine.h \ @@ -40,6 +41,7 @@ HEADERS += \ mythread.h \ parsebackuplist.h \ systembackupproxy.h \ + systemrestoreproxy.h \ udisksystembackupproxy.h \ workerfactory.h @@ -61,6 +63,7 @@ SOURCES += \ mythread.cpp \ parsebackuplist.cpp \ systembackupproxy.cpp \ + systemrestoreproxy.cpp \ udisksystembackupproxy.cpp \ workerfactory.cpp diff --git a/backup-daemon/mybackupmanager.cpp b/backup-daemon/mybackupmanager.cpp index 23a8f28..4c08b97 100755 --- a/backup-daemon/mybackupmanager.cpp +++ b/backup-daemon/mybackupmanager.cpp @@ -110,6 +110,7 @@ int MyBackupManager::checkEnv(const BackupWrapper& backupWrapper) */ int MyBackupManager::goBackup(const BackupWrapper& backupWrapper) { + qDebug("MyBackupManager::goBackup invoke begin"); if (m_isActive || !lock(backupWrapper.m_frontUid)) { emit sendEnvCheckResult(int(BackupResult::LOCK_PROGRAM_FAIL)); return int(BackupResult::LOCK_PROGRAM_FAIL); @@ -149,6 +150,7 @@ int MyBackupManager::goBackup(const BackupWrapper& backupWrapper) workerThread.start(); + qDebug("MyBackupManager::goBackup invoke end"); return int(BackupResult::BACKUP_RESULT_INIT); } @@ -159,8 +161,48 @@ int MyBackupManager::goBackup(const BackupWrapper& backupWrapper) */ int MyBackupManager::goRestore(const BackupWrapper& backupWrapper) { - Q_UNUSED(backupWrapper) - return 0; + qDebug("MyBackupManager::goRestore invoke begin"); + if (m_isActive || !lock(backupWrapper.m_frontUid)) { + emit sendEnvCheckResult(int(BackupResult::LOCK_PROGRAM_FAIL)); + return int(BackupResult::LOCK_PROGRAM_FAIL); + } + + Worker* worker = WorkerFactory::createWorker(backupWrapper.m_type, backupWrapper.m_iPosition); + if (nullptr == worker) { + emit sendEnvCheckResult(int(BackupResult::NO_FOUND_DEALCLASS)); + return int(BackupResult::NO_FOUND_DEALCLASS); + } + + worker->setParam(backupWrapper); + connect(worker, &Worker::checkResult, this, [&](int result) { + emit this->sendEnvCheckResult(result); + + switch (result) { + case int(BackupResult::CHECK_ENV_SUCCESS) : + case int(BackupResult::MKSQUASHFS_START_SUCCESS) : + case int(BackupResult::BACKUP_START_SUCCESS) : + break; + default: + this->finished(); + break; + } + }); + connect(worker, &Worker::progress, this, [&](int rate) { + emit this->progress(int(BackupState::WORKING), rate); + }); + connect(worker, &Worker::workResult, this, [&] (bool result) { + emit this->backupFinished(result); + this->finished(); + }); + worker->moveToThread(&workerThread); + connect(&workerThread, &MyThread::started, worker, &Worker::doWork); + connect(&workerThread, &MyThread::finished, worker, &Worker::deleteLater); + connect(&workerThread, &MyThread::cancelWork, worker, &Worker::cancel); + + workerThread.start(); + + qDebug("MyBackupManager::goRestore invoke end"); + return int(BackupResult::BACKUP_RESULT_INIT); } /** @@ -170,6 +212,7 @@ int MyBackupManager::goRestore(const BackupWrapper& backupWrapper) */ int MyBackupManager::deleteBackupPoint(const BackupWrapper& backupWrapper) { + qDebug("MyBackupManager::deleteBackupPoint invoke begin"); if (m_isActive || !lock(backupWrapper.m_frontUid)) { emit sendEnvCheckResult(int(BackupResult::LOCK_PROGRAM_FAIL)); return int(BackupResult::LOCK_PROGRAM_FAIL); @@ -192,6 +235,7 @@ int MyBackupManager::deleteBackupPoint(const BackupWrapper& backupWrapper) workerThread.start(); + qDebug("MyBackupManager::deleteBackupPoint invoke end"); return int(BackupResult::BACKUP_RESULT_INIT); } diff --git a/backup-daemon/parsebackuplist.cpp b/backup-daemon/parsebackuplist.cpp index 8a6869f..0c238ed 100755 --- a/backup-daemon/parsebackuplist.cpp +++ b/backup-daemon/parsebackuplist.cpp @@ -245,7 +245,7 @@ ParseBackupList::BackupPoint ParseBackupList::findBackupPointByUuid(const QStrin } /** - * @brief 获取最后一次系统备份 + * @brief 获取最后一次系统备份,排除自动备份点 * @return */ ParseBackupList::BackupPoint ParseBackupList::getLastSysBackupPoint() @@ -267,6 +267,10 @@ ParseBackupList::BackupPoint ParseBackupList::getLastSysBackupPoint() if (eleType.isNull() || (BackupType::BACKUP_SYSTEM != eleType.text().toInt() && BackupType::INC_BACKUP_SYSTEM != eleType.text().toInt())) continue; + QDomElement eleUuid = node.firstChildElement(UUID); + if (eleUuid.isNull() || eleUuid.text() == AUTO_BACKUP_UUID) + continue; + QDomElement eleState = node.firstChildElement(STATE); QString type = eleState.text(); if (eleState.isNull() || eleState.text() != QString(STATUE_BACKUP_FINESHED)) diff --git a/backup-daemon/parsebackuplist.h b/backup-daemon/parsebackuplist.h index b7f1845..d5f4d4f 100755 --- a/backup-daemon/parsebackuplist.h +++ b/backup-daemon/parsebackuplist.h @@ -25,7 +25,7 @@ public: QString m_backupName; // 备份时间 QString m_time; - // 本地备份还是U盘备份: 0-本地备份;1-移动设备备份 + // 本地备份还是U盘备份: 0-本地备份;1-移动设备备份;2-异机备份(仅用于业务场景标记,不用于持久化记录) int m_iPosition = -1; // 操作类型,如:系统备份, 系统还原 int m_type = -1; diff --git a/backup-daemon/systemrestoreproxy.cpp b/backup-daemon/systemrestoreproxy.cpp new file mode 100644 index 0000000..9433a67 --- /dev/null +++ b/backup-daemon/systemrestoreproxy.cpp @@ -0,0 +1,382 @@ +#include "systemrestoreproxy.h" +#include +#include +#include +#include +#include +#include "../common/utils.h" +#include "mymountproxy.h" + +IMPLEMENT_DYNCREATE(SystemRestoreProxy) + +/** + * @brief 构造函数 + */ +SystemRestoreProxy::SystemRestoreProxy() +{ + m_bSuccess = false; + m_p = nullptr; +} + +/** + * @brief 析构函数 + */ +SystemRestoreProxy::~SystemRestoreProxy() +{ + delete m_p; +} + +/** + * @brief 环境检测 + * @return false,检测失败;true,检测成功 + */ +bool SystemRestoreProxy::checkEnvEx() +{ + qDebug() << "SystemRestoreProxy::checkEnvEx invoke begin"; + + // 1、检测.user.txt是否存在 + m_userFile = Utils::getSysRootPath() + BACKUP_SNAPSHOTS_PATH + "/" + m_backupWrapper.m_uuid + "/" + PATHS_USER_FILE; + m_userFile.replace("//", "/"); + if (!Utils::filsExists(m_userFile)) { + qCritical(".user.txt文件不存在"); + emit checkResult(int(BackupResult::WRITE_BACKUP_PATHS_TO_USER_FAILED)); + return false; + } + + // 2、检测.exclude.user.txt是否存在 + m_excludeUserFile = Utils::getSysRootPath() + BACKUP_SNAPSHOTS_PATH + "/" + m_backupWrapper.m_uuid + "/" + EXCLUDE_PATHS_USER_FILE; + m_excludeUserFile.replace("//", "/"); + if (!Utils::filsExists(m_excludeUserFile)) { + qCritical(".exclude.user.txt文件不存在"); + emit checkResult(int(BackupResult::WRITE_EXCLUDE_BACKUP_PATHS_TO_USER_FAILED)); + return false; + } + + // 3、检测还原点是否存在 + m_backupPath = Utils::getSysRootPath() + BACKUP_SNAPSHOTS_PATH + "/" + m_backupWrapper.m_uuid + "/data"; + m_backupPath.replace("//", "/"); + if (Utils::isDirEmpty(m_backupPath)) { + qCritical("还原点{uuid}/data目录不存在"); + emit checkResult(int(BackupResult::INC_NOT_FOUND_DIR)); + return false; + } + + qDebug() << "SystemRestoreProxy::checkEnvEx invoke end"; + return true; +} + +/** + * @brief 执行还原逻辑 + */ +void SystemRestoreProxy::doWorkEx() +{ + qDebug() << "SystemRestoreProxy::doWorkEx invoke begin"; + + // 1、校验 + if (!checkEnvEx()) + return ; + + // 2、还原efi(兼容旧版本的备份) + if (!restoreEfi()) { + qCritical("/boot/efi目录同步失败"); + emit checkResult(int(BackupResult::EFI_RSYNC_FAIL)); + return ; + } + + // 3、还原系统 + restoreSystem(); + + qDebug() << "SystemRestoreProxy::doWorkEx invoke end"; +} + +/** + * @brief 还原efi(兼容旧版本的备份) + * @return + */ +bool SystemRestoreProxy::restoreEfi() +{ + qDebug() << "SystemRestoreProxy::restoreEfi invoke begin"; + + // 是否有/boot/efi目录 + QString efiPath = Utils::getSysRootPath() + "/boot/efi"; + efiPath.replace("//", "/"); + if (!Utils::isDirEmpty(efiPath)) { + // 1、修复源数据 + repairEfi(); + + // 2、重新rw读写挂载 + remountEfi(); + + // 3、同步efi + return rsyncEfi(); + } + + qDebug() << "SystemRestoreProxy::restoreEfi invoke end"; + return true; +} + +/** + * @brief 修复efi目录 + */ +void SystemRestoreProxy::repairEfi() +{ + QString qsEfiPath = m_backupPath + "/efi"; + if (!Utils::isDirEmpty(qsEfiPath)) { + // 存在/efi说明是老备份数据,尽量修正老数据 + QStringList args; + args << "-f"; + args << qsEfiPath; + QString newPath = m_backupPath + "/boot"; + args << newPath; + QProcess::execute("mv", args); + } +} + +/** + * @brief 重新rw读写挂载efi分区 + */ +void SystemRestoreProxy::remountEfi() +{ + QString mountPath = Utils::getSysRootPath() + "/boot/efi"; + mountPath.replace("//", "/"); + QStringList args; + args << "-o" + << "rw,remount" + << mountPath; + QProcess::execute("mount", args); +} + +/** + * @brief 同步efi + */ +bool SystemRestoreProxy::rsyncEfi() +{ + QString efiPath = m_backupPath + "/boot/efi/"; + if (Utils::isDirEmpty(efiPath)) + efiPath = efiPath = m_backupPath + "/efi/"; + if (Utils::isDirEmpty(efiPath)) + return true; + + QStringList args = getRsyncArgs(SystemRestoreScene::EFI_RESTORE); + QString mountPath = Utils::getSysRootPath() + "/boot/efi/"; + mountPath.replace("//", "/"); + + args << efiPath << mountPath; + + m_p = new RsyncPathToDirProcess(this); + bool result = m_p->start(args); + delete m_p; + m_p = nullptr; + + return result; +} + +/** + * @brief 根据场景获取rsync命令参数 + * @param scene,场景 + * @return 组装好的rsync的参数信息 + */ +QStringList SystemRestoreProxy::getRsyncArgs(SystemRestoreScene scene) +{ + QStringList args; + args << "-avAHXr"; + args << "--info=progress2"; + args << "--no-inc-recursive"; + args << "--ignore-missing-args"; + args << "--delete"; + + QDir dataDir(m_srcPath + "/data"); + QFile file(m_srcPath + "/etc/uid_list"); + QDir efiDir(m_srcPath + "/boot/efi"); + QStringList excludes; + + switch (scene) { + case SystemRestoreScene::RESTORE_SYSTEM_WITH_DATA : + args << "--exclude=/home"; + args << "--exclude=/root"; + if (Utils::isHuawei990()) { + args << "--exclude=/data"; + } else { + args << "--exclude=/data/usershare"; + } + // 保留指纹数据,用户密码、角色、权限、生物识别等信息不需要改变 + args << "--exclude=/var/lib/biometric-auth"; + args << "--exclude=/data/sec_storage_data"; + args << "--exclude=/etc/passwd"; + args << "--exclude=/etc/shadow"; + args << "--exclude=/etc/group"; + args << "--exclude=/etc/gshadow"; + args << "--exclude=/etc/sudoers"; + args << "--exclude=/data/home"; + args << "--exclude=/data/root"; + + // 此处不要break,因为还需要排除SYSTEM_RESTORE中的项 + + case SystemRestoreScene::SYSTEM_RESTORE : + // 还原工具不还原自身 + args << "--exclude=/usr/bin/backup-daemon"; + args << "--exclude=/usr/bin/kybackup"; + args << "--exclude=/usr/bin/mount_fstab_efi"; + args << "--exclude=/usr/bin/backup-auto-efi"; + args << "--exclude=/usr/bin/backup-auto"; + args << "--exclude=/usr/bin/rsync"; + args << "--exclude=/usr/share/rsync"; + args << "--exclude=/usr/share/initramfs-tools/hooks/kybackup-hooks"; + args << "--exclude=/usr/share/initramfs-tools/scripts/local-bottom/kybackup"; + + // 以前的出厂备份和grub备份没有备份/data,还原时需要判断/data是否存在,如不存在需要屏蔽掉,不然会将主机上的/data删除,造成问题 + // 此为兼容以前备份的老数据而改,等以后老的备份估计不存在了可已去掉 + if (!dataDir.exists()) { + args << "--exclude=/data"; + } + + if (!file.exists()) { + args << "--exclude=/etc/uid_list"; + } + + // 为兼容以前的老备份数据,增加下面几行 + if (efiDir.isEmpty()) { + args << QString("--exclude=/boot/efi"); + } + // 系统安装后有的会将/data/home /data/root挂载到的/home /root上,实际文件是存放在/data/home /data/root下面 + Utils::excludeFstabBindPath(excludes); + for (const QString& item : excludes) { + QDir itemDir(m_srcPath + item); + // 以后统一用/home /root这种路径, 兼容老备份数据(原来的U盘备份,在mksquashfs时排除bind挂载的任意一方时,都备份不上) + if (item == "/data/home") { + QDir homeDir(m_srcPath + "/home"); + if (!homeDir.isEmpty()) { + args << QString("--exclude=/data/home"); + } else if (!itemDir.isEmpty()) { + args << QString("--exclude=/home"); + } else { + args << QString("--exclude=/data/home"); + args << QString("--exclude=/home"); + } + continue; + } else if (item == "/data/root") { + QDir homeDir(m_srcPath + "/root"); + if (!homeDir.isEmpty()) { + args << QString("--exclude=/data/root"); + } else if (!itemDir.isEmpty()) { + args << QString("--exclude=/root"); + } else { + args << QString("--exclude=/data/root"); + args << QString("--exclude=/root"); + } + continue; + } + + args << QString("--exclude=") + item; + } + + args << "--exclude-from" << m_excludeUserFile; + args << "--files-from" << m_userFile; + + break ; + case SystemRestoreScene::EFI_RESTORE : + break ; + default: + return args; + } + + return args; +} + +/** + * @brief 系统还原 + */ +void SystemRestoreProxy::restoreSystem() +{ + // 本地系统备份没有img挂载,故下面两个路径相等 + m_srcPath = m_backupPath; + QString destPath = Utils::getSysRootPath(); + + QStringList args; + // 自动更新的备份还原时保留用户数据 + if (m_curUuid == AUTO_BACKUP_UUID || m_backupWrapper.m_type == BackupType::RESTORE_SYSTEM_WITH_DATA) { + args = getRsyncArgs(SystemRestoreScene::RESTORE_SYSTEM_WITH_DATA); + } else { + args = getRsyncArgs(SystemRestoreScene::SYSTEM_RESTORE); + } + + args << m_srcPath + "/"; + args << destPath + "/"; + + m_p = new RsyncPathToDirProcess(this); + connect(m_p, &RsyncPathToDirProcess::progress, this, &SystemRestoreProxy::progress); + connect(m_p, &RsyncPathToDirProcess::finished, this, [&](bool result) { + if (result) { + QString time = QDateTime::currentDateTime().toString("yy-MM-dd hh:mm:ss"); + Utils::writeBackupLog(time + "," + m_curUuid + "," + QString::number(m_backupWrapper.m_type) + ",,," + QString::number(m_backupWrapper.m_frontUid)); + + Utils::updateSyncFile(); + QString fileIfSync = Utils::getSysRootPath() + FILE_IF_SYNC; + fileIfSync.replace("//", "/"); + QFileInfo file(fileIfSync); + QDateTime beginTime = file.fileTime(QFileDevice::FileModificationTime); + if (Utils::isHuawei990() && FACTORY_BACKUP_UUID == m_curUuid && m_backupWrapper.m_type == BackupType::RESTORE_SYSTEM) { + // 出厂还原有的机器上删除/home/xxx有残留,故在此再强制删除一下,妈的sudo rm -rf命令一遍还删除不了(报错:无法删除/home/xx/.config:目录非空,应该是删除后又自动生成了),多删除几次还不是非常干净,贱 ^_^ + removeHome(Utils::getSysRootPath() + "/home"); + removeHome(Utils::getSysRootPath() + "/data/home"); + } + Utils::wait(10); + Utils::updateSyncFile(); + while (1) { + Utils::wait(2); + QFileInfo file1(fileIfSync); + QDateTime UpdateTime = file1.fileTime(QFileDevice::FileModificationTime); + if (UpdateTime > beginTime) + break; + } + + emit this->workResult(result); + Utils::wait(2); + reboot(RB_AUTOBOOT); + } + emit this->workResult(result); + }); + + m_p->start(args, false); +} + +/** + * @brief 删除home子目录,此处主要用于删除用户残留信息 + * @param path + * @return + */ +void SystemRestoreProxy::removeHome(const QString& path) +{ + QDir dir(path); + if (dir.exists()) { + bool needRm = false; + QString subcmd("rm -rf "); + QString delDirs; + QStringList list = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs); + for (const QString& item : list) { + // 出厂备份里面的/home/oem保留 + if (item == "oem") + continue ; + + QString subPath = path; + subPath += "/"; + subPath += item; + QDir subDir(subPath); + subDir.removeRecursively(); + + delDirs += "${rootmnt}"; + delDirs += path; + delDirs += "/"; + delDirs += item; + delDirs += " "; + + needRm = true; + } + + if (needRm) { + QString cmd = QString("bash -c \"%1\" ").arg(subcmd + delDirs); + cmd.replace("${rootmnt}", ""); + QProcess::execute(cmd); + } + } +} diff --git a/backup-daemon/systemrestoreproxy.h b/backup-daemon/systemrestoreproxy.h new file mode 100644 index 0000000..dba4dd5 --- /dev/null +++ b/backup-daemon/systemrestoreproxy.h @@ -0,0 +1,71 @@ +#ifndef SYSTEMRESTOREPROXY_H +#define SYSTEMRESTOREPROXY_H + +#include "workerfactory.h" +#include "myprocess/rsyncpathtodirprocess.h" +#include "parsebackuplist.h" + +class SystemRestoreProxy : public Worker +{ + Q_OBJECT + DECLARE_DYNCREATE(SystemRestoreProxy) +public: + // 系统还原的几种场景 + enum SystemRestoreScene { + SYSTEM_RESTORE, // 系统还原 + RESTORE_SYSTEM_WITH_DATA, // 保留用户数据还原 + EFI_RESTORE, // efi还原 + }; + + explicit SystemRestoreProxy(); + virtual ~SystemRestoreProxy(); + +public: + // 环境检测 + virtual bool checkEnvEx(); + + // 任务处理 + virtual void doWorkEx(); + +private: + // 还原efi + bool restoreEfi(); + // 修复efi目录 + void repairEfi(); + // 以读写方式重新挂载efi分区 + void remountEfi(); + // 同步efi + bool rsyncEfi(); + // 系统还原 + void restoreSystem(); + // 删除home下的用户家目录 + void removeHome(const QString& path); + + /** + * @brief 根据场景获取rsync命令参数 + * @param scene,场景 + * @return 组装好的rsync的参数信息 + */ + QStringList getRsyncArgs(SystemRestoreScene scene); + + // .user.txt文件路径 + QString m_userFile; + // .exclude.user.txt文件路径 + QString m_excludeUserFile; + // 备份数据所在的data目录 + QString m_backupPath; + + // 是否还原成功 + bool m_bSuccess; + // 当前备份uuid + QString m_curUuid; + // 当前还原源目录 + QString m_srcPath; + // 备份进程 + RsyncPathToDirProcess *m_p; + // 当前备份节点 + ParseBackupList::BackupPoint m_backupPoint; + +}; + +#endif // SYSTEMRESTOREPROXY_H diff --git a/common/mydefine.h b/common/mydefine.h index 959f70d..2c7e18f 100755 --- a/common/mydefine.h +++ b/common/mydefine.h @@ -27,6 +27,8 @@ #define LOCK_FILE_PATH "/tmp/lock" #define PROC_LOG "/var/log/backup.log" +#define FILE_IF_SYNC "/etc/file_if_sync" + #define BACKUP_CLI_NAME "kybackup" #define AUTO_BACKUP_UUID "{01234567-0123-0123-0123-0123456789ab}" @@ -99,7 +101,7 @@ struct BackupWrapper { QStringList m_backupPaths; // 备份需要排除的路径 QStringList m_backupExcludePaths; - // 备份目标路径(前缀),在向移动设备备份时使用它来指定对应的移动设备路径 + // 移动设备挂载路径:备份目标路径/还原点路径(前缀),在向移动设备备份(或从移动设备还原)时使用它来指定对应的移动设备路径 QString m_prefixDestPath; // 备注信息 QString m_note; @@ -109,8 +111,10 @@ struct BackupWrapper { int m_frontUid = -1; // 备份用户所属组id int m_gid = -1; + // 是否异机备份点: 0-本机备份;1-异机备份 + int m_isOtherMachine = 0; - // follow are not input parameters + // 下面参数不是入参,是服务端自行跟随业务场景设置 // 是否增量备份 bool m_bIncrement = false; // 新增备份点时增量备份的基准uuid @@ -156,8 +160,9 @@ enum class BackupState { */ enum BackupPosition { - LOCAL, - UDISK, + LOCAL, // 本地磁盘 + UDISK, // 移动设备 + OTHER // 移动设备中的其它机器的备份点 }; /** @@ -199,11 +204,11 @@ enum class BackupResult { WRITE_STORAGEINFO_UPDATE_ITEM_FAIL, // 增量备份未找到对应的uuid INC_NOT_FOUND_UUID, - // 增量备份未找到对应的目录 + // 增量备份(或还原)未找到对应的目录 INC_NOT_FOUND_DIR, - // 将备份路径写入/backup/snapshots/{uuid}/.user.txt失败 + // 将备份路径写入(或读出)/backup/snapshots/{uuid}/.user.txt失败 WRITE_BACKUP_PATHS_TO_USER_FAILED, - // 将备份路径写入/backup/snapshots/{uuid}/.exclude.user.txt失败 + // 将备份路径写入(或读出)/backup/snapshots/{uuid}/.exclude.user.txt失败 WRITE_EXCLUDE_BACKUP_PATHS_TO_USER_FAILED, // /backup备份空间不足 BACKUP_CAPACITY_IS_NOT_ENOUGH, diff --git a/common/utils.cpp b/common/utils.cpp index 726407b..72460fa 100755 --- a/common/utils.cpp +++ b/common/utils.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "mylittleparse.h" #include "mydefine.h" @@ -378,7 +380,7 @@ void Utils::excludeFstabBindPath(QStringList &excludes) continue; fields << field; } - // 配置文件/etc/fstab每行6个域,第二个域为挂载路径, 第一个域就是待排除路径 + // 配置文件/etc/fstab每行6个域,第二个域为挂载路径, 为统一路径使用挂接点,排除第一个域 if (6 == fields.size()) excludes << fields.at(0); } @@ -565,6 +567,23 @@ bool Utils::filsExists(const QString &fileName) return (stat(fileName.toStdString().data(), &buffer) == 0); } +/** + * @brief 判断目录是否为空 + * @param fullDirName 目录路径 + * @return true-目录不存在或为空目录; false-非空目录 + */ +bool Utils::isDirEmpty(const QString& fullDirName) +{ + QDir dir(fullDirName); + if (!dir.exists()) + return true; + + if (dir.isEmpty()) + return true; + + return false; +} + /** * @brief 记录备份日志 * @param line,日志内容 @@ -792,15 +811,19 @@ bool Utils::isRunning(const QString &processName) } /** - * @brief 判断是否990或9006C处理器 + * @brief 判断是否990或9006C、PANGU处理器 * @return bool */ bool Utils::isHuawei990() { QString result; Utils::executeCMD("cat /proc/cpuinfo | grep -i \"kirin.*9.0\"", result); - if (result.isEmpty()) - return false; + if (result.isEmpty()) { + Utils::executeCMD("cat /proc/cpuinfo | grep -i \"PANGU\"", result); + if (result.isEmpty()) + return false; + } + return true; } @@ -1082,3 +1105,36 @@ void Utils::deleteBackupUniqueRecord(const QString& backupName) udisk_unique_settings.setIniCodec(QTextCodec::codecForName("utf-8")); udisk_unique_settings.remove(backupName); } + +/** + * @brief 前后两次调用,然后取文件修改时间用于判断缓存数据是否落盘 + * @return + */ +bool Utils::updateSyncFile() +{ + QString fileIfSync = Utils::getSysRootPath() + FILE_IF_SYNC; + fileIfSync.replace("//", "/"); + QFile file(fileIfSync); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return false; + } + + QTextStream out(&file); + out << "sync" << endl; + file.close(); + + return true; +} + +/** + * @brief 用事件循环替换sleep,以便事件尽量得到处理 + * @param sec,等待时间,单位秒 + * @author zhaominyong + * @since 2021/05/29 + */ +void Utils::wait(uint sec) +{ + QEventLoop loop; + QTimer::singleShot(1000 * sec, &loop, SLOT(quit())); + loop.exec(); +} diff --git a/common/utils.h b/common/utils.h index 5b092a7..3a8d570 100755 --- a/common/utils.h +++ b/common/utils.h @@ -152,6 +152,13 @@ public: */ static bool filsExists(const QString &fileName); + /** + * @brief 判断目录是否为空 + * @param fullDirName 目录路径 + * @return true-目录不存在或为空目录; false-非空目录 + */ + static bool isDirEmpty(const QString& fullDirName); + /** * @brief 记录备份日志 * @param line,日志内容 @@ -280,6 +287,20 @@ public: */ static void deleteBackupUniqueRecord(const QString& backupName); + /** + * @brief 前后两次调用,然后取文件修改时间用于判断缓存数据是否落盘 + * @return + */ + static bool updateSyncFile(); + + /** + * @brief 用事件循环替换sleep,以便事件尽量得到处理 + * @param sec,等待时间,单位秒 + * @author zhaominyong + * @since 2021/05/29 + */ + static void wait(uint sec); + private: // 系统根目录,默认"/" static QString m_sysRootPath; diff --git a/kybackup/module/selectrestorepoint.cpp b/kybackup/module/selectrestorepoint.cpp index 48b508f..d6fe5ef 100644 --- a/kybackup/module/selectrestorepoint.cpp +++ b/kybackup/module/selectrestorepoint.cpp @@ -19,19 +19,20 @@ SelectRestorePoint::SelectRestorePoint(QWidget *parent, BackupPointType backupTy m_tableWidget->hideColumn(Column_Index::Backup_State); initTableWidget(); + // 刷新 + MyPushButton * buttonRefresh = new MyPushButton; + buttonRefresh->setText(tr("Refresh")); + + // 确定按钮 MyPushButton * buttonOk = new MyPushButton; buttonOk->setText(tr("Ok")); - MyPushButton * buttonCancel = new MyPushButton; - buttonCancel->setText(tr("Cancel")); m_bottomLayout->addStretch(); + m_bottomLayout->addWidget(buttonRefresh); + m_bottomLayout->addSpacing(10); m_bottomLayout->addWidget(buttonOk); - m_bottomLayout->addWidget(buttonCancel); - - connect(buttonCancel, &MyPushButton::clicked, this, [=]() { - this->close(); - }); + connect(buttonRefresh, &MyPushButton::clicked, this, &SelectRestorePoint::initTableWidget); connect(this, &SelectRestorePoint::udiskChange, this, &SelectRestorePoint::initTableWidget); connect(buttonOk, &MyPushButton::clicked, this, [=](){ @@ -50,6 +51,9 @@ SelectRestorePoint::SelectRestorePoint(QWidget *parent, BackupPointType backupTy backupPoint.m_uuid = this->text(curRow, Column_Index::UUID); backupPoint.m_time = this->text(curRow, Column_Index::Backup_Time); backupPoint.m_path = this->text(curRow, Column_Index::Prefix_Path); + QString dev = this->text(curRow, Column_Index::Backup_Device); + if (dev.startsWith(tr("Udisk Device:"))) + backupPoint.m_iPosition = BackupPosition::OTHER; emit selected(backupPoint); this->close(); diff --git a/kybackup/module/systembackup.cpp b/kybackup/module/systembackup.cpp index 4857c73..551fdd0 100755 --- a/kybackup/module/systembackup.cpp +++ b/kybackup/module/systembackup.cpp @@ -280,7 +280,7 @@ void SystemBackup::initThirdWidget() MyLabel *label4 = new MyLabel(tr("finished"), third); label4->setGeometry(551, 72, 164, 30); - //------------ 中部布局begin------------- + // ------------ 中部布局begin------------- QVBoxLayout *vlayout = new QVBoxLayout(third); vlayout->addSpacing(180); QHBoxLayout *hlayout = new QHBoxLayout; @@ -390,7 +390,7 @@ void SystemBackup::initThirdWidget() vlayout->addLayout(hlayout); vlayout->addStretch(); third->setLayout(vlayout); - //------------ 中部布局end------------- + // ------------ 中部布局end------------- // 开始检测 connect(this, &SystemBackup::startCheckEnv, this, [=]() { @@ -399,7 +399,7 @@ void SystemBackup::initThirdWidget() movie->start(); resultLogo->setVisible(false); // 环境检测中,请等待 - bigTitle->setDeplayText(tr("Being checked, wait a moment ...")); + bigTitle->setDeplayText(tr("Checking, wait a moment ...")); dot1->setBackgroundColor(Qt::black); dot2->setBackgroundColor(Qt::black); // 备份过程中不要做其它操作,以防数据丢失 diff --git a/kybackup/module/systemrestore.cpp b/kybackup/module/systemrestore.cpp index 729996e..587aef4 100755 --- a/kybackup/module/systemrestore.cpp +++ b/kybackup/module/systemrestore.cpp @@ -8,7 +8,6 @@ #include #include -#include "../component/clicklabel.h" #include "../component/circlelabel.h" #include "../component/mycheckbox.h" #include "../component/myiconlabel.h" @@ -28,8 +27,10 @@ SystemRestore::SystemRestore(QWidget *parent) : m_isRetainUserData = false; m_isFactoryRestore = false; m_pInterface = nullptr; + // 界面手写代码创建,作为练手 initFirstWidget(); + initSecondWidget(); } SystemRestore::~SystemRestore() @@ -58,9 +59,6 @@ void SystemRestore::initFirstWidget() labelRestore_firstPage->setFixedWidth(500); labelRestore_firstPage->setFixedHeight(48); labelRestore_firstPage->move(41, 120); - // 默认水平左对齐,上下居中对齐;故不需要设置 - // labelRestore_firstPage->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - // labelRestore_firstPage->setText(tr("System Backup")); QFont font; font.setBold(true); font.setPixelSize(36); @@ -186,6 +184,12 @@ void SystemRestore::on_next_clicked(bool checked) */ void SystemRestore::on_button_beginRestore_clicked(bool checked) { + on_next_clicked(); + emit this->startCheckEnv(); + return; + + // -------以上为测试代码------- + Q_UNUSED(checked) // 出厂还原,不用去选择备份点 @@ -206,11 +210,337 @@ void SystemRestore::on_button_beginRestore_clicked(bool checked) connect(selectRestoreDialog, &SelectRestorePoint::selected, this, [=](ParseBackupList::BackupPoint backupPoint){ this->m_uuid = backupPoint.m_uuid; this->m_devPath = backupPoint.m_path; + this->m_isOtherMachine = backupPoint.m_iPosition == BackupPosition::OTHER ? true : false; }); selectRestoreDialog->exec(); selectRestoreDialog->deleteLater(); } on_next_clicked(); + emit this->startCheckEnv(); } +/** + * @brief 初始化第二界面 + */ +void SystemRestore::initSecondWidget() +{ + QWidget *second = new QWidget; + + // 流程进度提示栏 + CircleLable *one = new CircleLable("1", second, 24, QColor(COLOR_BLUE)); + LineLabel *line1 = new LineLabel(second, QColor(COLOR_BLUE), QSize(200, 24)); + CircleLable *two = new CircleLable("2", second); + LineLabel *line2 = new LineLabel(second, QColor(COLOR_GRAY), QSize(200, 24)); + CircleLable *three = new CircleLable("3", second); + QHBoxLayout *layoutLine1 = new QHBoxLayout; + layoutLine1->addStretch(); + layoutLine1->addWidget(one); + layoutLine1->addWidget(line1); + layoutLine1->addWidget(two); + layoutLine1->addWidget(line2); + layoutLine1->addWidget(three); + layoutLine1->addStretch(); + + MyLabel *label1 = new MyLabel(tr("checking"), second); + label1->setIsOriginal(true); + label1->setFontColor(QColor(COLOR_BLUE)); + MyLabel *label2 = new MyLabel(tr("restoring"), second); + label2->setIsOriginal(true); + MyLabel *label3 = new MyLabel(tr("finished"), second); + label3->setIsOriginal(true); + QHBoxLayout *layoutLine2 = new QHBoxLayout; + layoutLine2->addSpacing(100); + layoutLine2->addWidget(label1); + layoutLine2->addStretch(); + layoutLine2->addWidget(label2); + layoutLine2->addStretch(); + layoutLine2->addWidget(label3); + layoutLine2->addSpacing(100); + + // ------------ 中部布局begin------------- + QWidget *centerFont = new QWidget(second); + QVBoxLayout *vlayoutCenterFont = new QVBoxLayout; + + // 第一行 + QHBoxLayout *hlayoutCenterFont1 = new QHBoxLayout; + // 检测等待图标 + QLabel *loadingGif = new QLabel(centerFont); + loadingGif->setFixedSize(20,20); + // 环境检测等待动画 + QMovie *movie = new QMovie(":/images/loading.gif", QByteArray(), centerFont); + loadingGif->setMovie(movie); + hlayoutCenterFont1->addWidget(loadingGif); + // 检测结果对错图标 + QLabel *resultLogo = new QLabel(centerFont); + resultLogo->setFixedSize(20,20); + hlayoutCenterFont1->addWidget(resultLogo); + // 检测中大标题 + MyLabel *bigTitle = new MyLabel(centerFont); + bigTitle->setFontSize(24); + bigTitle->setMaximumWidth(700); + hlayoutCenterFont1->addWidget(bigTitle); + hlayoutCenterFont1->addStretch(); + vlayoutCenterFont->addLayout(hlayoutCenterFont1); + + // 第二行 + QHBoxLayout *hlayoutCenterFont2 = new QHBoxLayout; + hlayoutCenterFont2->addSpacing(10); + // 检测中的记录:黑点1和文字1 + CircleLable *dot1 = new CircleLable(QString(""), centerFont, 6, Qt::black); + hlayoutCenterFont2->addWidget(dot1); + hlayoutCenterFont2->addSpacing(5); + MyLabel *labelCheck1 = new MyLabel(centerFont); + labelCheck1->setMinimumWidth(400); + labelCheck1->setMaximumWidth(600); + labelCheck1->setIsOriginal(true); + labelCheck1->setWordWrap(true); + labelCheck1->adjustSize(); + hlayoutCenterFont2->addWidget(labelCheck1); + hlayoutCenterFont2->addStretch(); + vlayoutCenterFont->addLayout(hlayoutCenterFont2); + + // 第三行 + QHBoxLayout *hlayoutCenterFont3 = new QHBoxLayout; + hlayoutCenterFont3->addSpacing(10); + // 检测中的记录:黑点2和文字2 + CircleLable *dot2 = new CircleLable(QString(""), centerFont, 6, Qt::black); + hlayoutCenterFont3->addWidget(dot2); + hlayoutCenterFont3->addSpacing(5); + MyLabel *labelCheck2 = new MyLabel(centerFont); + labelCheck2->setMinimumWidth(400); + labelCheck2->setMaximumWidth(600); + labelCheck2->setIsOriginal(true); + labelCheck2->setWordWrap(true); + labelCheck2->adjustSize(); + hlayoutCenterFont3->addWidget(labelCheck2); + hlayoutCenterFont3->addStretch(); + vlayoutCenterFont->addLayout(hlayoutCenterFont3); + + // 第四行 + vlayoutCenterFont->addSpacing(30); + + // 第五行 + QHBoxLayout *hlayoutCenterFont5 = new QHBoxLayout; + hlayoutCenterFont5->addStretch(); + // 上一步按钮 + MyPushButton *preStep = new MyPushButton(centerFont); + preStep->setFixedSize(97, 36); + preStep->setText(tr("back")); + preStep->setEnabled(true); + preStep->setAutoRepeat(true); + connect(preStep, &MyPushButton::clicked, this, &SystemRestore::on_pre_clicked); + hlayoutCenterFont5->addWidget(preStep); + hlayoutCenterFont5->addSpacing(20); + // 下一步按钮 + MyPushButton *nextStep = new MyPushButton(centerFont); + nextStep->setFixedSize(97, 36); + nextStep->setText(tr("next")); + nextStep->setEnabled(true); + nextStep->setAutoRepeat(true); + connect(nextStep, &MyPushButton::clicked, this, [=](bool checked) { + this->on_next_clicked(checked); + }); + hlayoutCenterFont5->addWidget(nextStep); + // 重新检测按钮 + MyPushButton *recheck = new MyPushButton(centerFont); + recheck->setFixedSize(97, 36); + recheck->setText(tr("recheck")); + recheck->setEnabled(true); + recheck->setAutoRepeat(true); + connect(recheck, &MyPushButton::clicked, this, [=](bool checked) { + Q_UNUSED(checked) + emit this->startCheckEnv(); + }); + hlayoutCenterFont5->addWidget(recheck); + hlayoutCenterFont5->addStretch(); + vlayoutCenterFont->addLayout(hlayoutCenterFont5); + + centerFont->setLayout(vlayoutCenterFont); + + // ------------ 中部布局end------------- + + QHBoxLayout *layoutLine3 = new QHBoxLayout; + layoutLine3->addStretch(); + layoutLine3->addWidget(centerFont); + layoutLine3->addStretch(); + + // 布局 + QVBoxLayout *vlayout = new QVBoxLayout; + vlayout->addSpacing(40); + vlayout->addLayout(layoutLine1); + vlayout->addLayout(layoutLine2); + vlayout->addSpacing(50); + vlayout->addLayout(layoutLine3); + vlayout->addStretch(); + second->setLayout(vlayout); + + // 开始检测 + connect(this, &SystemRestore::startCheckEnv, this, [=]() { + this->m_systemRestoreState = SystemRestoreState::CHECKING; + loadingGif->setVisible(true); + movie->start(); + resultLogo->setVisible(false); + // 环境检测中,请等待 + bigTitle->setDeplayText(tr("Checking, wait a moment ...")); + dot1->setBackgroundColor(Qt::black); + dot2->setBackgroundColor(Qt::black); + // 还原过程中不要做其它操作,以防数据丢失 + labelCheck1->setDeplayText(tr("Check whether the restore environment meets the requirements")); + // 检测还原环境是否满足 + labelCheck2->setDeplayText(tr("Do not perform other operations during restore to avoid data loss")); + preStep->setVisible(false); + nextStep->setVisible(false); + recheck->setVisible(false); + + this->on_checkEnv_start(); + }); + + // 检测结果 + connect(this, &SystemRestore::checkEnvResult, this, [=](bool result, const QString &errMsg, const QString &errTip) { + loadingGif->setVisible(false); + movie->stop(); + + if (result) { + QIcon icon = QIcon::fromTheme("ukui-dialog-success", QIcon(":/symbos/ukui-dialog-success.png")); + resultLogo->setPixmap(icon.pixmap(QSize(20,20))); + resultLogo->setVisible(true); + // 检测成功 + bigTitle->setDeplayText(tr("Succeeded to check the environment")); + // 还原完成后将自动重启 + labelCheck1->setDeplayText(tr("The system will reboot automatically after the restore is successful")); + dot2->setBackgroundColor(COLOR_YELLOW); + labelCheck2->setFontColor(COLOR_YELLOW); + labelCheck2->setFontWordWrap(true); + // 请确保电脑已连接电源或电量超过60% + labelCheck2->setDeplayText(tr("Make sure the computer is plugged in or the battery level is above 60%")); + dot1->setVisible(true); + dot2->setVisible(true); + labelCheck1->setVisible(true); + labelCheck2->setVisible(true); + nextStep->setVisible(true); + recheck->setVisible(false); + } else { + QIcon icon = QIcon::fromTheme("dialog-error.png", QIcon(":/symbos/dialog-error.png")); + resultLogo->setPixmap(icon.pixmap(QSize(20,20))); + resultLogo->setVisible(true); + // 环境校验失败 + bigTitle->setDeplayText(tr("Failed to check the environment")); + labelCheck1->setDeplayText(errMsg); + labelCheck2->setDeplayText(errTip); + if (errMsg.isEmpty()) { + dot1->setVisible(false); + labelCheck1->setVisible(false); + } else { + dot1->setVisible(true); + labelCheck1->setVisible(true); + } + if (errTip.isEmpty()) { + dot2->setVisible(false); + labelCheck2->setVisible(false); + } else { + dot2->setVisible(true); + labelCheck2->setVisible(true); + } + recheck->setVisible(true); + nextStep->setVisible(false); + } + + preStep->setVisible(true); + }); + + addWidget(second); +} + +/** + * @brief 开始进行环境检测 + */ +void SystemRestore::on_checkEnv_start() +{ + GlobelBackupInfo::inst().setIsBusy(true); + m_pInterface = new ComKylinBackupManagerInterface("com.kylin.backup", "/", QDBusConnection::systemBus(), this); + connect(m_pInterface, &ComKylinBackupManagerInterface::sendEnvCheckResult, this, &SystemRestore::on_checkEnv_end); + + // 是否已存在备份、还原等操作 + bool isActive = false; + if(int(BackupState::BACKUP_STATE_INIT) != m_pInterface->getBackupState(isActive)){ + on_checkEnv_end(int(BackupResult::OTHER_BACKUP_OR_RESTORE_RUNNING)); + + return; + } + + BackupWrapper backupWrapper; + backupWrapper.m_type = m_isRetainUserData ? BackupType::RESTORE_SYSTEM_WITH_DATA : BackupType::RESTORE_SYSTEM; + backupWrapper.m_iPosition = m_devPath.isEmpty() ? BackupPosition::LOCAL : BackupPosition::UDISK; + backupWrapper.m_prefixDestPath = m_devPath; + backupWrapper.m_isOtherMachine = m_isOtherMachine ? 1 : 0; + backupWrapper.m_frontUid = getuid(); + backupWrapper.m_gid = getgid(); + m_pInterface->checkEnv(backupWrapper); +} + +/** + * @brief 环境检测结束 + * @param result, 环境校验结果 + */ +void SystemRestore::on_checkEnv_end(int result) +{ + m_systemRestoreState = SystemRestoreState::IDEL; + bool bRst = false; + QString errMsg, errTip; + switch (result) { + case int(BackupResult::LOCK_PROGRAM_FAIL): + // 程序锁定失败,请重试 + errMsg = tr("Program lock failed, please retry"); + // 可能有其它备份/还原等任务在执行 + errTip = tr("There may be other backups or restores being performed"); + break; + case int(BackupResult::NO_FOUND_DEALCLASS): + // 不支持的任务类型 + errMsg = tr("Unsupported task type"); + // 没有找到相应的处理逻辑 + errTip = tr("No processing logic was found in the service"); + break; + case int(BackupResult::BACKUP_PARTITION_MOUNT_FAIL): + // 备份分区挂载失败 + errMsg = tr("Failed to mount the backup partition"); + // 检查是否有备份分区 + errTip = tr("Check whether there is a backup partition"); + break; + case int(BackupResult::UDISK_FILESYSTEM_TYPE_IS_VFAT): + // 移动设备的文件系统是vfat格式 + errMsg = tr("The filesystem of device is vfat format"); + // 请换成ext4、ntfs等格式 + errTip = tr("Please change filesystem format to ext3、ext4 or ntfs"); + break; + case int(BackupResult::UDISK_FILESYSTEM_IS_READONLY): + // 移动设备是只读的 + errMsg = tr("The device is read only"); + // 请修改为可读写权限的 + errTip = tr("Please chmod to rw"); + break; + case int(BackupResult::BACKUP_CAPACITY_IS_NOT_ENOUGH): + // 备份空间不足 + errMsg = tr("The storage for backup is not enough"); + // 建议释放空间后重试 + errTip = tr("Retry after release space"); + break; + case int(BackupResult::OTHER_BACKUP_OR_RESTORE_RUNNING): + // 其它备份还原等操作正在执行 + errMsg = tr("Other backup or restore task is being performed"); + // 请稍后重试 + errTip = tr("Please try again later"); + break; + default: + bRst = true; + break; + } + + emit checkEnvResult(bRst, errMsg, errTip); + GlobelBackupInfo::inst().setIsBusy(false); + disconnect(m_pInterface, &ComKylinBackupManagerInterface::sendEnvCheckResult, this, &SystemRestore::on_checkEnv_end); + delete m_pInterface; + m_pInterface = nullptr; +} + + diff --git a/kybackup/module/systemrestore.h b/kybackup/module/systemrestore.h index 04701d9..4e9aa63 100755 --- a/kybackup/module/systemrestore.h +++ b/kybackup/module/systemrestore.h @@ -19,10 +19,8 @@ public: enum SystemRestorePage { HOME_PAGE, // 首页 - SELECT_PATH_PAGE, // 选择备份路径页 CHECK_ENV_PAGE, // 环境检测页 - NAME_BACKUP_PAGE, // 备份命名页 - BACKUP_PAGE, // 备份中页 + BACKUP_PAGE, // 还原中页 LAST_PAGE, // 结束页 }; @@ -32,11 +30,18 @@ public: private: void initFirstWidget(); + void initSecondWidget(); + +signals: + void startCheckEnv(); + void checkEnvResult(bool result, const QString &errMsg = "", const QString &errTip = ""); public slots: void on_pre_clicked(bool checked = false); void on_next_clicked(bool checked = false); void on_button_beginRestore_clicked(bool checked = false); + void on_checkEnv_start(); + void on_checkEnv_end(int result); private: // 是否保留用户数据 @@ -49,6 +54,9 @@ private: QString m_uuid; // 还原点的UUID QString m_devPath; // 如果是从移动设备进行还原,此中保存移动设备挂载路径 + bool m_isOtherMachine; + // 系统备份状态 + int m_systemRestoreState; }; #endif // SYSTEMRESTORE_H