文件夹是否有修改的检查

linux 文件夹是否有修改的检查

实现某个功能时,需要检查某个文件和对应的文件夹是否有修改:
记录 inode + 修改时间 + 文件大小确保文件没有被修改

在 Linux 中:

  • 每个文件由一个 inode number(inode 节点号) 唯一标识
  • 这个唯一性 只在同一个文件系统(filesystem / 分区)内保证
static string ensureTrailingSlash(const std::string& path) {
    if (path.back() != '/')
        return path + "/caches/";
    return path + "caches/";
}

string
findValidImage(const string& layout, const abLoadOption& opt) {
  if (!s_cachePaths.empty()) {
    for (const auto& it : s_cachePaths) {
      string path = ensureTrailingSlash(it) + generateDirName(layout, opt).toStdString();
      if (isImageValid(path, layout))
        return path;
    }
  } else {
    string path = getDefaultImageDir(layout, opt, false);
    if (isImageValid(path, layout))
      return path;
  }
  return "";
}

string
getWriteImageDir(const string& layout, const abLoadOption& opt) {
  for (const auto& it : s_cachePaths) {
    string path = ensureTrailingSlash(it) + generateDirName(layout, opt).toStdString();
    if (createWritableDir(path))
      return path;
  }
  string path = getDefaultImageDir(layout, opt, false);
  if (createWritableDir(path))
    return path;
  return "";
}

bool
createWritableDir(const string& path) {
  QDir dir(path.c_str());

  if (!dir.mkpath(dir.absolutePath()))
    return false;

  QFileInfo info(path.c_str());
  return info.exists() && info.isDir() && info.isWritable();
}

string
getDefaultImageDir(const string& layout, const abLoadOption& opt, bool isCreate) {
  auto tryCreateWritableDir = [&](const QString& path) -> bool {
    QDir dir(path);
    if (isCreate) {
      if (!dir.mkpath(dir.absolutePath()))
        return false;
    }

    QFileInfo info(path);
    return info.exists() && info.isDir() && info.isWritable();
  };

  QString dirName = generateDirName(layout, opt);
  QStringList candidates = {
    "./." + QString(abApp::getBinName()) + "/caches/" + dirName,  // current dir
    QDir::homePath() + "/." + abApp::getBinName() + "/caches/" + dirName // home dir
  };

  for (const QString& candidate : candidates) {
    if (tryCreateWritableDir(candidate)) {
        return candidate.toStdString();
    }
  }

  return "";
}

QString
generateDirName(const string& layout, const abLoadOption& opt) {
  struct stat fileStat;
  if (::stat(layout.c_str(), &fileStat) != 0)
      return "";

  QFileInfo fi(layout.c_str());
  QString fileName = fi.fileName();

  // 文件名 + inode + size + mtime + optkey + version
  QString res = QString("%1_%2_%3_%4_%5_%6")
    .arg(fileName)
    .arg(fileStat.st_ino)
    .arg(fileStat.st_size)
    .arg(fileStat.st_mtime)
    .arg(getOpenOptionKey(opt))
    .arg(s_imageVersion.c_str());
  return res;
}

quint64
calculateDirectorySnapshot(const QString& imageDir) {
  quint64 snapshot = 0;

  QDirIterator it(imageDir, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
                  QDirIterator::Subdirectories);

  while (it.hasNext()) {
    QString path = it.next();
    QFileInfo fi(path);

    snapshot += fi.size();
    snapshot += fi.lastModified().toTime_t();
  }

  return snapshot;
}

bool
isImageValid(const string& imageDir, const string& layout) {
  struct stat fileStat;
  if (::stat(layout.c_str(), &fileStat) != 0) return false;

  string metaPath = imageDir + s_metaData;
  QFile metaFile(metaPath.c_str());
  if (!metaFile.exists() ||
      !metaFile.open(QIODevice::ReadOnly | QIODevice::Text))
    return false;

  QTextStream in(&metaFile);
  QString inodeLine = in.readLine();
  QString sizeLine = in.readLine();
  QString mtimeLine = in.readLine();
  QString cacheDirSnapshotLine = in.readLine();
  metaFile.close();

  if (inodeLine != QString::number(fileStat.st_ino)) return false;
  if (sizeLine != QString::number(fileStat.st_size)) return false;
  if (mtimeLine != QString::number(fileStat.st_mtime)) return false;

  quint64 currentSnapshot = calculateDirectorySnapshot(imageDir.c_str());
  quint64 savedSnapshot = cacheDirSnapshotLine.toULongLong();

  if (currentSnapshot != savedSnapshot) return false;

  return true;
}

bool
saveMetadata(const string& imageDir, const string& layout) {
  QDir dir;
  if (!dir.exists(imageDir.c_str())) {
    if (!dir.mkpath(imageDir.c_str())) return false;
  }

  struct stat fileStat;
  if (::stat(layout.c_str(), &fileStat) != 0) return false;

  quint64 cacheSnapshot = calculateDirectorySnapshot(imageDir.c_str());

  QFile metaFile(QString::fromStdString(imageDir + s_metaData));
  if (!metaFile.open(QIODevice::WriteOnly | QIODevice::Text)) return false;

  QTextStream out(&metaFile);
  out << QString::number(fileStat.st_ino) << "\n";
  out << QString::number(fileStat.st_size) << "\n";
  out << QString::number(fileStat.st_mtime) << "\n";
  out << QString::number(cacheSnapshot) << "\n";

  out.flush();
  if (metaFile.error() != QFile::NoError) {
    fprintf(stderr, "File write failed: %s\n", qPrintable(metaFile.errorString()));
    return false;
  }

  metaFile.close();
  return true;
}

uint64_t
getOpenOptionKey(const abLoadOption& opt) {
  auto fnv1a64 = [](const char* data, size_t len) -> uint64_t {
    uint64_t hash = 0xcbf29ce484222325ULL;
    for (size_t i = 0; i < len; ++i) {
      hash ^= static_cast<uint8_t>(data[i]);
      hash *= 0x100000001b3ULL;
    }
    return hash;
  };

  zaUInt32 flag = opt.getFlag();
  if (s_splitUngridArray) flag |= 0x100;

  uint64_t key = static_cast<uint64_t>(flag);

  double hw = opt.zeroPathHalfWidth();
  if (!zaIsFloatEqual(hw, 0)) {
    uint64_t hwBits = *reinterpret_cast<uint64_t*>(&hw);
    key ^= hwBits + 0x9e3779b97f4a7c15ULL + (key << 6) + (key >> 2);
  }

  if (opt.layerMap()) {
    std::string layerStr = opt.layerMap()->toSortString();
    uint64_t layerHash = fnv1a64(layerStr.c_str(), layerStr.size());
    key ^= layerHash + 0x9e3779b97f4a7c15ULL + (key << 6) + (key >> 2);
  }

  return key;
}

bool
removeDir(const QString &dirPath) {
  QDir dir(dirPath);
  if (!dir.exists())
    return false;

  QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries);
  for (const QFileInfo &entry : entries) {
    QString path = entry.absoluteFilePath();
    if (entry.isDir()) {
      if (!removeDir(path))
        return false;
    } else {
      if (!QFile::remove(path))
        return false;
    }
  }

  if (!dir.rmdir(dirPath))
    return false;

  return true;
}

string
replaceLastFolder(const string& path) {
  string trimmed = path;

  while (!trimmed.empty() && trimmed.back() == '/')
    trimmed.pop_back();

  size_t pos = trimmed.find_last_of('/');
  if (pos == string::npos)
    return "caches";

  string parent = trimmed.substr(0, pos);
  return parent + "/caches/";
}
posted @ 2025-11-25 10:53  卑以自牧lq  阅读(4)  评论(0)    收藏  举报