/*
*
* Copyright (C) 2023, KylinSoft Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Authors: iaom
*/
#include "search-dir.h"
#include "config.h"
#include "volume-manager.h"
#include
SearchDir::SearchDir(const QString &path, bool generateBlackList)
{
if (!QFile::exists(path)) {
m_error = NotExists;
} else {
m_path = path;
if(generateBlackList) {
this->handleBlackListGenerate();
}
}
}
bool SearchDir::operator ==(const SearchDir &rhs) const
{
return (m_path == rhs.m_path);
}
SearchDir::ErrorInfo SearchDir::error()
{
return m_error;
}
QString SearchDir::errorString()
{
switch (m_error) {
case ErrorInfo::Successful:
return "Add search dir successful";
case ErrorInfo::Duplicated:
return "Path or its parent dir has been added";
case ErrorInfo::UnderBlackList:
return "Path is or under blacklist";
case ErrorInfo::RepeatMount1:
return "Path is in repeat mounted devices and another path which is in the same device has been indexed";
case ErrorInfo::RepeatMount2:
return "Another path which is in the same device has been indexed";
case ErrorInfo::NotExists:
return "Path is not exists";
}
return QString();
}
QString SearchDir::getPath() const
{
return m_path;
}
void SearchDir::setBlackList(const QStringList &blackList)
{
m_blackList = blackList;
}
QStringList SearchDir::getBlackList() const
{
return m_blackList;
}
void SearchDir::generateBlackList()
{
m_blackList.clear();
this->handleBlackListGenerate();
}
QStringList SearchDir::blackListOfDir(const QString &dirPath)
{
//手动刷新挂载信息
VolumeManager::self()->refresh();
QStringList blackListOfDir;
for (const QStringList & mountPoints: VolumeManager::self()->getDuplicates()) {
QString topRepeatedMountPoint;
for (const QString &mountPoint : mountPoints) {
//该目录下是否有两个文件夹(有可能为父子关系)是重复挂载的关系
if (mountPoint.startsWith(dirPath + "/") or mountPoint == dirPath) {
if (topRepeatedMountPoint.isEmpty()) {
topRepeatedMountPoint = mountPoint;
continue;
}
//重复挂载时保留最上层的挂载点
if (topRepeatedMountPoint.startsWith(mountPoint + "/")) {
blackListOfDir.append(topRepeatedMountPoint);
topRepeatedMountPoint = mountPoint;
} else {
blackListOfDir.append(mountPoint);
}
}
}
}
blackListOfDir << handleSubVolumes4SingleDir(dirPath);
return blackListOfDir;
}
void SearchDir::handleBlackListGenerate()
{
//手动刷新挂载信息
VolumeManager::self()->refresh();
QStringList searchDirs = Config::self()->searchDirs();
//目录已被索引(根目录被添加过直接返回)
for (const QString searchDir : searchDirs) {
if (searchDir == m_path || searchDir == "/") {
m_error = Duplicated;
return;
}
}
//根目录特殊处理
if (m_path == "/") {
m_blackList << Config::self()->globalBlackList() << searchDirs;
for (const QStringList &mountPoints: VolumeManager::self()->getDuplicates()) {
QStringList repeatMountPoints = mountPoints;
if (mountPoints.contains("/")) {
repeatMountPoints.removeAll("/");
m_blackList << repeatMountPoints;
continue;
}
bool excludeAll(false);
for (const QString &mountPoint : mountPoints) {
for (const QString &searchDir : searchDirs) {
//之前已索引重复挂载设备挂载点或其子目录,则排除其他目录
if (searchDir.startsWith(mountPoint + "/") || searchDir == mountPoint) {
repeatMountPoints.removeAll(mountPoint);
break;
}
//重复挂载点在已索引目录下,该挂载点全排除
if (mountPoint.startsWith(searchDir + "/")) {
excludeAll = true;
break;
}
}
if (excludeAll) {
m_blackList << repeatMountPoints;
break;
}
}
//不需要全排除且没有需要特别保留的挂载点,默认留第一个
if (!excludeAll && repeatMountPoints == mountPoints) {
repeatMountPoints.removeFirst();
}
m_blackList << repeatMountPoints;
}
m_blackList.append(handleSubVolumes4SingleDir("/"));
m_blackList.removeDuplicates();
return;
}
//处理要添加索引的路径与全局黑名单中路径为父子关系的情况
for (const QString& blackListPath : Config::self()->globalBlackList()) {
if (m_path.startsWith(blackListPath + "/") or m_path == blackListPath) {
m_error = UnderBlackList;
return;
}
if (blackListPath.startsWith(m_path + "/")) {
m_blackList.append(blackListPath);
}
}
//重复挂载情况
for (const QStringList & mountPoints: VolumeManager::self()->getDuplicates()) {
QString topRepeatedMountPoint;
for (const QString &mountPoint : mountPoints) {
if (mountPoint.startsWith(m_path + "/") or mountPoint == m_path) {
if (topRepeatedMountPoint.isEmpty()) {
topRepeatedMountPoint = mountPoint;
continue;
} else if (topRepeatedMountPoint.startsWith(mountPoint)) {
m_blackList.append(topRepeatedMountPoint);
topRepeatedMountPoint = mountPoint;
} else {
m_blackList.append(mountPoint);
}
}
}
//排除要添加的目录为某设备的重复挂载目录,并且之前已索引过该设备其他挂载目录或其父目录的情况
bool pathToBeAddedIsRepeatedDevice = false;
bool pathToBeAddedHasRepeatedDevice = false;
bool addedPathIsRepeatedDevice = false;
bool addedPathHasRepeatedDevice = false;
QString addedRelativeDir;
QString repeatedDir;
for (const QString &addedPath : searchDirs) {
for (const QString &mountPoint : mountPoints) {
//要添加索引路径在重复挂载设备路径下(1)
if (m_path.startsWith(mountPoint + "/") or mountPoint == m_path) {
repeatedDir = mountPoint;
pathToBeAddedIsRepeatedDevice = true;
}
//重复挂载设备路径在要添加索引路径下(2)
if (mountPoint.startsWith(m_path + "/")) {
repeatedDir = mountPoint;
pathToBeAddedHasRepeatedDevice = true;
}
//已索引路径在重复挂载设备路径下(3)
if (addedPath.startsWith(mountPoint + "/") or mountPoint == addedPath) {
addedRelativeDir = addedPath;
addedRelativeDir.remove(0, mountPoint.length());
addedPathIsRepeatedDevice = true;
}
//重复挂载设备路径在已索引路径下(4)
if (mountPoint.startsWith(addedPath + "/")) {
addedPathHasRepeatedDevice = true;
}
//(1)(4)直接返回
if (pathToBeAddedIsRepeatedDevice and addedPathHasRepeatedDevice) {
m_error = RepeatMount1;
return;
}
//(2)(4)将要添加索引目录相应的重复挂载路径添加到黑名单
if (pathToBeAddedHasRepeatedDevice and addedPathHasRepeatedDevice) {
m_blackList.append(repeatedDir);
break;
}
//(1)(3)将已索引路径的前缀替换为要添加路径的前缀(前缀为mountPoint),判断替换后路径是否在要索引路径下,如果是则返回,否则将替换后路径添加到黑名单
if (pathToBeAddedIsRepeatedDevice and addedPathIsRepeatedDevice) {
QString pathAfterReplace = repeatedDir + addedRelativeDir;
if (m_path.startsWith(pathAfterReplace) or m_path == pathAfterReplace) {
m_error = RepeatMount2;
return;
} else {
m_blackList.append(pathAfterReplace);
break;
}
}
//(2)(3)将替换前缀后的已索引路径添加到黑名单
if (pathToBeAddedHasRepeatedDevice and addedPathIsRepeatedDevice) {
m_blackList.append(repeatedDir + addedRelativeDir);
break;
}
}
}
}
//处理自动挂载子卷下的目录
for (const Volume &volume : VolumeManager::self()->volumesHaveSubVolumes()) {
QMap subVolumeInfo = volume.subVolumes();
for (auto it = subVolumeInfo.constBegin(); it != subVolumeInfo.constEnd(); it++) {
QString subMountPoint = it.key();
for (const QString &duplicateMountPoint : volume.mountPoints()) {
if (subVolumeInfo.keys().contains(duplicateMountPoint)) {
continue;
}
QString spec = duplicateMountPoint + it.value(); //子卷对应目录
//要添加目录下存在子卷(要添加/data,但挂到/home的/data/home是子卷),若添加了/home则将/data/home排除
if (spec.startsWith(m_path + "/")) {
for (QString &searchDir : searchDirs) {
if (searchDir == subMountPoint || subMountPoint.startsWith(searchDir + "/")) {
m_blackList << spec;
}
if (searchDir.startsWith(subMountPoint + "/")) {
m_blackList << searchDir.replace(0, subMountPoint.length(), spec);
}
}
}
//要添加的目录是子卷或在子卷下(/data/home or /data/home/xxx)
if (m_path.startsWith(spec + "/") || m_path == spec) {
for (QString &searchDir : searchDirs) {
//已添加挂载点或其上层目录
if (subMountPoint.startsWith(searchDir + "/") || searchDir == subMountPoint) {
m_error = RepeatMount1;
return;
}
//已添加挂载点下其他目录
if (searchDir.startsWith(subMountPoint + "/")) {
QString tmp = searchDir;
tmp.replace(0, subMountPoint.length(), spec);
if (tmp == m_path) {
m_error = RepeatMount2;
return;
} else if (tmp.startsWith(m_path + "/")) {//已添加的子卷子目录替换前缀后在要添加目录下
m_blackList << tmp;
}
}
}
}
//要添加的目录是挂载点或在挂载点下(/home or /home/xxx)
if (m_path.startsWith(subMountPoint + "/") || m_path == subMountPoint) {
for (QString &searchDir : searchDirs) {
//已添加子卷或其上层目录
if (spec.startsWith(searchDir + "/") || searchDir == spec) {
m_error = RepeatMount1;
return;
}
//已添加子卷下其他目录
if (searchDir.startsWith(spec + "/")) {
QString tmp = searchDir;
tmp.replace(0, spec.length(), subMountPoint);
if (tmp == m_path) {
m_error = RepeatMount2;
return;
} else if (tmp.startsWith(m_path + "/")) {//已添加的子卷子目录替换前缀后在要添加目录下
m_blackList << tmp;
}
}
}
}
}
}
}
//要添加目录下存在已索引目录
for (const QString &searchDir : searchDirs) {
if (m_path.startsWith(searchDir + "/")) {
m_error = Duplicated;
return;
}
if (searchDir.startsWith(m_path + "/")) {
m_blackList.append(searchDir);
}
}
m_blackList.removeDuplicates();
}
QStringList SearchDir::handleSubVolumes4SingleDir(const QString &searchDir) {
QStringList blackList;
for (const Volume &volume : VolumeManager::self()->volumesHaveSubVolumes()) {
QMap subVolumeInfo = volume.subVolumes();
for (auto it = subVolumeInfo.constBegin(); it != subVolumeInfo.constEnd(); it++) {
QString subMountPoint = it.key();
for (const QString &duplicateMountPoint : volume.mountPoints()) {
if (subVolumeInfo.keys().contains(duplicateMountPoint)) {
continue;
}
QString spec = duplicateMountPoint + it.value(); //子卷对应目录
if (searchDir == "/") {
blackList << spec;
} else {
//排除搜索列表指定多个目录时子卷会重复包含的情况,比如同时指定/home和/data/home
QString tmp = searchDir;
if (searchDir.startsWith(subMountPoint + "/")) {
blackList << tmp.replace(0, subMountPoint.length(), spec);
}
//要添加目录下存在子卷(要添加/data,但挂到/home的/data/home是子卷),若添加了/home则将/data/home排除
if (searchDir.startsWith(spec + "/")) {
blackList << tmp.replace(0, spec.length(), subMountPoint);
}
if (spec.startsWith(searchDir + "/")) {
if (searchDir == subMountPoint || subMountPoint.startsWith(searchDir + "/")) {
blackList << spec;
}
}
}
}
}
}
return blackList;
}