mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-06 06:19:52 +08:00
386 lines
12 KiB
C++
386 lines
12 KiB
C++
#include "fileutils.h"
|
|
|
|
#include <QFile>
|
|
#include <QMimeDatabase>
|
|
#include <QDateTime>
|
|
#include <QTemporaryFile>
|
|
#include <QJsonDocument>
|
|
|
|
#include <core/exception.h>
|
|
#include <core/global.h>
|
|
|
|
#include "pathutils.h"
|
|
#include <QRandomGenerator>
|
|
|
|
using namespace vnotex;
|
|
|
|
QByteArray FileUtils::readFile(const QString &p_filePath)
|
|
{
|
|
QFile file(p_filePath);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
Exception::throwOne(Exception::Type::FailToReadFile,
|
|
QString("failed to read file: %1").arg(p_filePath));
|
|
}
|
|
|
|
return file.readAll();
|
|
}
|
|
|
|
QString FileUtils::readTextFile(const QString &p_filePath)
|
|
{
|
|
QFile file(p_filePath);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
Exception::throwOne(Exception::Type::FailToReadFile,
|
|
QString("failed to read file: %1").arg(p_filePath));
|
|
}
|
|
|
|
// TODO: determine the encoding of the text.
|
|
QString text(file.readAll());
|
|
file.close();
|
|
return text;
|
|
}
|
|
|
|
QJsonObject FileUtils::readJsonFile(const QString &p_filePath)
|
|
{
|
|
return QJsonDocument::fromJson(readFile(p_filePath)).object();
|
|
}
|
|
|
|
void FileUtils::writeFile(const QString &p_filePath, const QByteArray &p_data)
|
|
{
|
|
QFile file(p_filePath);
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
Exception::throwOne(Exception::Type::FailToWriteFile,
|
|
QString("failed to write to file: %1").arg(p_filePath));
|
|
}
|
|
|
|
file.write(p_data);
|
|
file.close();
|
|
}
|
|
|
|
void FileUtils::writeFile(const QString &p_filePath, const QString &p_text)
|
|
{
|
|
QFile file(p_filePath);
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
Exception::throwOne(Exception::Type::FailToWriteFile,
|
|
QString("failed to write to file: %1").arg(p_filePath));
|
|
}
|
|
|
|
QTextStream stream(&file);
|
|
stream << p_text;
|
|
file.close();
|
|
}
|
|
|
|
void FileUtils::writeFile(const QString &p_filePath, const QJsonObject &p_jobj)
|
|
{
|
|
writeFile(p_filePath, QJsonDocument(p_jobj).toJson());
|
|
}
|
|
|
|
void FileUtils::renameFile(const QString &p_path, const QString &p_name)
|
|
{
|
|
Q_ASSERT(PathUtils::isLegalFileName(p_name));
|
|
QString newFilePath(PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_path), p_name));
|
|
QFile file(p_path);
|
|
if (!file.exists() || !file.rename(newFilePath)) {
|
|
Exception::throwOne(Exception::Type::FailToRenameFile,
|
|
QString("failed to rename file: %1").arg(p_path));
|
|
}
|
|
}
|
|
|
|
bool FileUtils::childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name)
|
|
{
|
|
QDir dir(p_dirPath);
|
|
if (!dir.exists()) {
|
|
return false;
|
|
}
|
|
|
|
auto name = p_name.toLower();
|
|
auto children = dir.entryList(QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
|
|
for (const auto &child : children) {
|
|
if (child.toLower() == name) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FileUtils::existsCaseInsensitive(const QString &p_path)
|
|
{
|
|
return childExistsCaseInsensitive(PathUtils::parentDirPath(p_path), PathUtils::fileName(p_path));
|
|
}
|
|
|
|
void FileUtils::copyFile(const QString &p_filePath,
|
|
const QString &p_destPath,
|
|
bool p_move)
|
|
{
|
|
if (PathUtils::areSamePaths(p_filePath, p_destPath)) {
|
|
return;
|
|
}
|
|
|
|
QDir dir;
|
|
if (!dir.mkpath(PathUtils::parentDirPath(p_destPath))) {
|
|
Exception::throwOne(Exception::Type::FailToCreateDir,
|
|
QString("failed to create directory: %1").arg(PathUtils::parentDirPath(p_destPath)));
|
|
}
|
|
|
|
bool failed = false;
|
|
if (p_move) {
|
|
QFile file(p_filePath);
|
|
if (!file.rename(p_destPath)) {
|
|
failed = true;
|
|
}
|
|
} else {
|
|
if (!QFile::copy(p_filePath, p_destPath)) {
|
|
failed = true;
|
|
}
|
|
}
|
|
|
|
if (failed) {
|
|
Exception::throwOne(Exception::Type::FailToCopyFile,
|
|
QString("failed to copy file: %1 %2").arg(p_filePath, p_destPath));
|
|
}
|
|
}
|
|
|
|
void FileUtils::copyDir(const QString &p_dirPath,
|
|
const QString &p_destPath,
|
|
bool p_move)
|
|
{
|
|
if (PathUtils::areSamePaths(p_dirPath, p_destPath)) {
|
|
return;
|
|
}
|
|
|
|
if (QFileInfo::exists(p_destPath)) {
|
|
Exception::throwOne(Exception::Type::FailToCopyDir,
|
|
QString("target directory %1 already exists").arg(p_destPath));
|
|
}
|
|
|
|
// QDir.rename() could not move directory across dirves.
|
|
|
|
// Create target directory.
|
|
QDir destDir(p_destPath);
|
|
if (!destDir.mkpath(p_destPath)) {
|
|
Exception::throwOne(Exception::Type::FailToCreateDir,
|
|
QString("failed to create directory: %1").arg(p_destPath));
|
|
}
|
|
|
|
// Copy directory contents recursively.
|
|
QDir srcDir(p_dirPath);
|
|
auto nodes = srcDir.entryInfoList(QDir::Dirs
|
|
| QDir::Files
|
|
| QDir::Hidden
|
|
| QDir::NoSymLinks
|
|
| QDir::NoDotAndDotDot);
|
|
for (const auto &node : nodes) {
|
|
auto name = node.fileName();
|
|
if (node.isDir()) {
|
|
copyDir(srcDir.filePath(name), destDir.filePath(name), p_move);
|
|
} else {
|
|
Q_ASSERT(node.isFile());
|
|
copyFile(srcDir.filePath(name), destDir.filePath(name), p_move);
|
|
}
|
|
}
|
|
|
|
if (p_move) {
|
|
if (!destDir.rmdir(p_dirPath)) {
|
|
Exception::throwOne(Exception::Type::FailToRemoveDir,
|
|
QString("failed to remove source directory after move: %1").arg(p_dirPath));
|
|
}
|
|
}
|
|
}
|
|
|
|
QString FileUtils::renameIfExistsCaseInsensitive(const QString &p_path)
|
|
{
|
|
QFileInfo fi(p_path);
|
|
auto dirPath = fi.absolutePath();
|
|
auto baseName = fi.completeBaseName();
|
|
auto suffix = fi.suffix();
|
|
auto name = fi.fileName();
|
|
int idx = 1;
|
|
while (childExistsCaseInsensitive(dirPath, name)) {
|
|
name = QString("%1_%2").arg(baseName, QString::number(idx));
|
|
if (!suffix.isEmpty()) {
|
|
name += QStringLiteral(".") + suffix;
|
|
}
|
|
|
|
++idx;
|
|
}
|
|
|
|
return PathUtils::concatenateFilePath(dirPath, name);
|
|
}
|
|
|
|
void FileUtils::removeFile(const QString &p_filePath)
|
|
{
|
|
Q_ASSERT(!QFileInfo::exists(p_filePath) || QFileInfo(p_filePath).isFile());
|
|
QFile file(p_filePath);
|
|
if (!file.remove()) {
|
|
Exception::throwOne(Exception::Type::FailToRemoveFile,
|
|
QString("failed to remove file: %1").arg(p_filePath));
|
|
}
|
|
}
|
|
|
|
bool FileUtils::removeDirIfEmpty(const QString &p_dirPath)
|
|
{
|
|
QDir dir(p_dirPath);
|
|
if (!dir.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
if (!dir.rmdir(p_dirPath)) {
|
|
Exception::throwOne(Exception::Type::FailToRemoveFile,
|
|
QString("failed to remove directory: %1").arg(p_dirPath));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FileUtils::removeDir(const QString &p_dirPath)
|
|
{
|
|
QDir dir(p_dirPath);
|
|
if (!dir.removeRecursively()) {
|
|
Exception::throwOne(Exception::Type::FailToRemoveFile,
|
|
QString("failed to remove directory recursively: %1").arg(p_dirPath));
|
|
}
|
|
}
|
|
|
|
bool FileUtils::isPlatformNameCaseSensitive()
|
|
{
|
|
#if defined(Q_OS_WIN)
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool FileUtils::isText(const QString &p_filePath)
|
|
{
|
|
QMimeDatabase mimeDatabase;
|
|
auto mimeType = mimeDatabase.mimeTypeForFile(p_filePath);
|
|
const auto name = mimeType.name();
|
|
if (name.startsWith(QStringLiteral("text/")) || name == QStringLiteral("application/x-zerosize")) {
|
|
return true;
|
|
}
|
|
|
|
return mimeType.inherits(QStringLiteral("text/plain"));
|
|
}
|
|
|
|
bool FileUtils::isImage(const QString &p_filePath)
|
|
{
|
|
QMimeDatabase mimeDatabase;
|
|
auto mimeType = mimeDatabase.mimeTypeForFile(p_filePath);
|
|
if (mimeType.name().startsWith(QStringLiteral("image/"))) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QImage FileUtils::imageFromFile(const QString &p_filePath)
|
|
{
|
|
QImage img(p_filePath);
|
|
if (!img.isNull()) {
|
|
return img;
|
|
}
|
|
|
|
// @p_filePath may has a wrong suffix which indicates a wrong image format.
|
|
img.loadFromData(readFile(p_filePath));
|
|
return img;
|
|
}
|
|
|
|
QPixmap FileUtils::pixmapFromFile(const QString &p_filePath)
|
|
{
|
|
QPixmap pixmap;
|
|
pixmap.loadFromData(readFile(p_filePath));
|
|
return pixmap;
|
|
}
|
|
|
|
QString FileUtils::generateUniqueFileName(const QString &p_folderPath,
|
|
const QString &p_hints,
|
|
const QString &p_suffix)
|
|
{
|
|
auto fileName = generateRandomFileName(p_hints, p_suffix);
|
|
int suffixIdx = fileName.lastIndexOf(QLatin1Char('.'));
|
|
auto baseName = suffixIdx == -1 ? fileName : fileName.left(suffixIdx);
|
|
auto suffix = suffixIdx == -1 ? QStringLiteral("") : fileName.mid(suffixIdx);
|
|
int index = 1;
|
|
while (childExistsCaseInsensitive(p_folderPath, fileName)) {
|
|
fileName = QString("%1_%2%3").arg(baseName, QString::number(index++), suffix);
|
|
}
|
|
|
|
return fileName;
|
|
}
|
|
|
|
QString FileUtils::generateRandomFileName(const QString &p_hints, const QString &p_suffix)
|
|
{
|
|
Q_UNUSED(p_hints);
|
|
|
|
// Do not use toSecsSinceEpoch() here since we want a short name.
|
|
const QString timeStamp(QDateTime::currentDateTime().toString(QStringLiteral("sszzzmmHHyyMMdd")));
|
|
const QString baseName(QString::number(timeStamp.toLongLong() + QRandomGenerator::global()->generate()));
|
|
|
|
QString suffix;
|
|
if (!p_suffix.isEmpty()) {
|
|
suffix = QLatin1Char('.') + p_suffix.toLower();
|
|
}
|
|
|
|
return baseName + suffix;
|
|
}
|
|
|
|
QTemporaryFile *FileUtils::createTemporaryFile(const QString &p_suffix)
|
|
{
|
|
QString xx = p_suffix.isEmpty() ? QStringLiteral("XXXXXX") : QStringLiteral("XXXXXX.");
|
|
return new QTemporaryFile(QDir::tempPath() + QDir::separator() + xx + p_suffix);
|
|
}
|
|
|
|
QString FileUtils::generateFileNameWithSequence(const QString &p_folderPath,
|
|
const QString &p_baseName,
|
|
const QString &p_suffix)
|
|
{
|
|
auto fileName = p_suffix.isEmpty() ? p_baseName : p_baseName + QLatin1Char('.') + p_suffix;
|
|
auto suffix = p_suffix.isEmpty() ? QString() : QStringLiteral(".") + p_suffix;
|
|
int index = 1;
|
|
while (childExistsCaseInsensitive(p_folderPath, fileName)) {
|
|
fileName = QString("%1_%2%3").arg(p_baseName, QString::number(index++), suffix);
|
|
}
|
|
|
|
return fileName;
|
|
}
|
|
|
|
void FileUtils::removeEmptyDir(const QString &p_dirPath)
|
|
{
|
|
QDir dir(p_dirPath);
|
|
if (dir.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
auto childDirs = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
|
|
for (const auto &child : childDirs) {
|
|
const auto childPath = child.absoluteFilePath();
|
|
removeEmptyDir(childPath);
|
|
removeDirIfEmpty(childPath);
|
|
}
|
|
}
|
|
|
|
QStringList FileUtils::entryListRecursively(const QString &p_dirPath,
|
|
const QStringList &p_nameFilters,
|
|
QDir::Filters p_filters)
|
|
{
|
|
QStringList entries;
|
|
|
|
QDir dir(p_dirPath);
|
|
if (!dir.exists()) {
|
|
return entries;
|
|
}
|
|
|
|
const auto curEntries = dir.entryList(p_nameFilters, p_filters | QDir::NoDotAndDotDot);
|
|
for (const auto &e : curEntries) {
|
|
entries.append(PathUtils::concatenateFilePath(p_dirPath, e));
|
|
}
|
|
|
|
const auto subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
|
|
for (const auto &subdir : subdirs) {
|
|
const auto dirPath = PathUtils::concatenateFilePath(p_dirPath, subdir);
|
|
entries.append(entryListRecursively(dirPath, p_nameFilters, p_filters));
|
|
}
|
|
|
|
return entries;
|
|
}
|