mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
655 lines
19 KiB
C++
655 lines
19 KiB
C++
#include "vnotefile.h"
|
|
|
|
#include <QDir>
|
|
#include <QDebug>
|
|
#include <QTextEdit>
|
|
#include <QFileInfo>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QDebug>
|
|
|
|
#include "vdirectory.h"
|
|
|
|
VNoteFile::VNoteFile(VDirectory *p_directory,
|
|
const QString &p_name,
|
|
FileType p_type,
|
|
bool p_modifiable,
|
|
QDateTime p_createdTimeUtc,
|
|
QDateTime p_modifiedTimeUtc)
|
|
: VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc)
|
|
{
|
|
}
|
|
|
|
QString VNoteFile::fetchPath() const
|
|
{
|
|
return QDir(getDirectory()->fetchPath()).filePath(m_name);
|
|
}
|
|
|
|
QString VNoteFile::fetchBasePath() const
|
|
{
|
|
return getDirectory()->fetchPath();
|
|
}
|
|
|
|
QString VNoteFile::fetchImageFolderPath() const
|
|
{
|
|
return QDir(fetchBasePath()).filePath(getNotebook()->getImageFolder());
|
|
}
|
|
|
|
bool VNoteFile::useRelativeImageFolder() const
|
|
{
|
|
// Always use relative image folder.
|
|
return true;
|
|
}
|
|
|
|
QString VNoteFile::getImageFolderInLink() const
|
|
{
|
|
return getNotebook()->getImageFolder();
|
|
}
|
|
|
|
void VNoteFile::setName(const QString &p_name)
|
|
{
|
|
m_name = p_name;
|
|
}
|
|
|
|
bool VNoteFile::rename(const QString &p_name)
|
|
{
|
|
if (m_name == p_name) {
|
|
return true;
|
|
}
|
|
|
|
QString oldName = m_name;
|
|
|
|
VDirectory *dir = getDirectory();
|
|
Q_ASSERT(dir);
|
|
|
|
// Rename it in disk.
|
|
QDir diskDir(dir->fetchPath());
|
|
if (!diskDir.rename(m_name, p_name)) {
|
|
qWarning() << "fail to rename file" << m_name << "to" << p_name << "in disk";
|
|
return false;
|
|
}
|
|
|
|
m_name = p_name;
|
|
|
|
// Update parent directory's config file.
|
|
if (!dir->updateFileConfig(this)) {
|
|
m_name = oldName;
|
|
diskDir.rename(p_name, m_name);
|
|
return false;
|
|
}
|
|
|
|
// Can't not change doc type.
|
|
Q_ASSERT(m_docType == DocType::Unknown
|
|
|| m_docType == VUtils::docTypeFromName(m_name));
|
|
|
|
m_docType = VUtils::docTypeFromName(m_name);
|
|
|
|
qDebug() << "file renamed from" << oldName << "to" << m_name;
|
|
return true;
|
|
}
|
|
|
|
VDirectory *VNoteFile::getDirectory()
|
|
{
|
|
Q_ASSERT(parent());
|
|
return (VDirectory *)parent();
|
|
}
|
|
|
|
const VDirectory *VNoteFile::getDirectory() const
|
|
{
|
|
Q_ASSERT(parent());
|
|
return (const VDirectory *)parent();
|
|
}
|
|
|
|
VNotebook *VNoteFile::getNotebook()
|
|
{
|
|
return getDirectory()->getNotebook();
|
|
}
|
|
|
|
const VNotebook *VNoteFile::getNotebook() const
|
|
{
|
|
return getDirectory()->getNotebook();
|
|
}
|
|
|
|
QString VNoteFile::getNotebookName() const
|
|
{
|
|
return getDirectory()->getNotebookName();
|
|
}
|
|
|
|
QString VNoteFile::fetchRelativePath() const
|
|
{
|
|
return QDir(getDirectory()->fetchRelativePath()).filePath(m_name);
|
|
}
|
|
|
|
VNoteFile *VNoteFile::fromJson(VDirectory *p_directory,
|
|
const QJsonObject &p_json,
|
|
FileType p_type,
|
|
bool p_modifiable)
|
|
{
|
|
VNoteFile *file = new VNoteFile(p_directory,
|
|
p_json[DirConfig::c_name].toString(),
|
|
p_type,
|
|
p_modifiable,
|
|
QDateTime::fromString(p_json[DirConfig::c_createdTime].toString(),
|
|
Qt::ISODate),
|
|
QDateTime::fromString(p_json[DirConfig::c_modifiedTime].toString(),
|
|
Qt::ISODate));
|
|
|
|
// Attachment Folder.
|
|
file->m_attachmentFolder = p_json[DirConfig::c_attachmentFolder].toString();
|
|
|
|
// Attachments.
|
|
QJsonArray attachmentJson = p_json[DirConfig::c_attachments].toArray();
|
|
for (int i = 0; i < attachmentJson.size(); ++i) {
|
|
QJsonObject attachmentItem = attachmentJson[i].toObject();
|
|
file->m_attachments.push_back(VAttachment(attachmentItem[DirConfig::c_name].toString()));
|
|
}
|
|
|
|
// Tags.
|
|
QJsonArray tagsJson = p_json[DirConfig::c_tags].toArray();
|
|
for (int i = 0; i < tagsJson.size(); ++i) {
|
|
file->m_tags.append(tagsJson[i].toString());
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
QJsonObject VNoteFile::toConfigJson() const
|
|
{
|
|
QJsonObject item;
|
|
item[DirConfig::c_name] = m_name;
|
|
item[DirConfig::c_createdTime] = m_createdTimeUtc.toString(Qt::ISODate);
|
|
item[DirConfig::c_modifiedTime] = m_modifiedTimeUtc.toString(Qt::ISODate);
|
|
item[DirConfig::c_attachmentFolder] = m_attachmentFolder;
|
|
|
|
// Attachments.
|
|
QJsonArray attachmentJson;
|
|
for (int i = 0; i < m_attachments.size(); ++i) {
|
|
const VAttachment &item = m_attachments[i];
|
|
QJsonObject attachmentItem;
|
|
attachmentItem[DirConfig::c_name] = item.m_name;
|
|
attachmentJson.append(attachmentItem);
|
|
}
|
|
|
|
item[DirConfig::c_attachments] = attachmentJson;
|
|
|
|
// Tags.
|
|
QJsonArray tags;
|
|
for (auto const & tag : m_tags) {
|
|
tags.append(tag);
|
|
}
|
|
|
|
item[DirConfig::c_tags] = tags;
|
|
|
|
return item;
|
|
}
|
|
|
|
bool VNoteFile::deleteFile(QString *p_errMsg)
|
|
{
|
|
Q_ASSERT(!m_opened);
|
|
Q_ASSERT(parent());
|
|
|
|
bool ret = true;
|
|
|
|
// Delete local images if it is Markdown.
|
|
if (m_docType == DocType::Markdown) {
|
|
if (!deleteInternalImages()) {
|
|
ret = false;
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to delete images of this note."));
|
|
}
|
|
}
|
|
|
|
// Delete attachments.
|
|
if (!deleteAttachments()) {
|
|
ret = false;
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to delete attachments of this note."));
|
|
}
|
|
|
|
// Delete the file.
|
|
QString filePath = fetchPath();
|
|
if (VUtils::deleteFile(getNotebook(), filePath, false)) {
|
|
qDebug() << "deleted" << m_name << filePath;
|
|
} else {
|
|
ret = false;
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to delete the note file."));
|
|
qWarning() << "fail to delete" << m_name << filePath;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool VNoteFile::deleteInternalImages()
|
|
{
|
|
Q_ASSERT(parent() && m_docType == DocType::Markdown);
|
|
|
|
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(this,
|
|
ImageLink::LocalRelativeInternal);
|
|
int deleted = 0;
|
|
for (int i = 0; i < images.size(); ++i) {
|
|
if (VUtils::deleteFile(getNotebook(), images[i].m_path, false)) {
|
|
++deleted;
|
|
}
|
|
}
|
|
|
|
qDebug() << "delete" << deleted << "images for" << m_name << fetchPath();
|
|
|
|
return deleted == images.size();
|
|
}
|
|
|
|
bool VNoteFile::addAttachment(const QString &p_file)
|
|
{
|
|
if (p_file.isEmpty() || !QFileInfo::exists(p_file)) {
|
|
return false;
|
|
}
|
|
|
|
QString folderPath = fetchAttachmentFolderPath();
|
|
QString name = VUtils::fileNameFromPath(p_file);
|
|
Q_ASSERT(!name.isEmpty());
|
|
// For attachments, we do not use complete base name.
|
|
// abc.tar.gz should be abc_001.tar.gz instead of abc.tar_001.gz.
|
|
name = VUtils::getFileNameWithSequence(folderPath, name, false);
|
|
QString destPath = QDir(folderPath).filePath(name);
|
|
if (!VUtils::copyFile(p_file, destPath, false)) {
|
|
return false;
|
|
}
|
|
|
|
m_attachments.push_back(VAttachment(name));
|
|
|
|
if (!getDirectory()->updateFileConfig(this)) {
|
|
qWarning() << "fail to update config of file" << m_name
|
|
<< "in directory" << fetchBasePath();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QString VNoteFile::fetchAttachmentFolderPath()
|
|
{
|
|
QString folderPath = QDir(fetchBasePath()).filePath(getNotebook()->getAttachmentFolder());
|
|
if (m_attachmentFolder.isEmpty()) {
|
|
m_attachmentFolder = VUtils::getRandomFileName(folderPath);
|
|
}
|
|
|
|
folderPath = QDir(folderPath).filePath(m_attachmentFolder);
|
|
if (!QFileInfo::exists(folderPath)) {
|
|
QDir dir;
|
|
if (!dir.mkpath(folderPath)) {
|
|
qWarning() << "fail to create attachment folder of notebook" << m_name << folderPath;
|
|
}
|
|
}
|
|
|
|
return folderPath;
|
|
}
|
|
|
|
bool VNoteFile::deleteAttachments(bool p_omitMissing)
|
|
{
|
|
if (m_attachments.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
QVector<QString> attas;
|
|
for (int i = 0; i < m_attachments.size(); ++i) {
|
|
attas.push_back(m_attachments[i].m_name);
|
|
}
|
|
|
|
return deleteAttachments(attas, p_omitMissing);
|
|
}
|
|
|
|
bool VNoteFile::deleteAttachments(const QVector<QString> &p_names,
|
|
bool p_omitMissing)
|
|
{
|
|
if (p_names.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
QDir dir(fetchAttachmentFolderPath());
|
|
bool ret = true;
|
|
for (int i = 0; i < p_names.size(); ++i) {
|
|
int idx = findAttachment(p_names[i]);
|
|
if (idx == -1) {
|
|
ret = false;
|
|
continue;
|
|
}
|
|
|
|
m_attachments.remove(idx);
|
|
|
|
QString filePath = dir.filePath(p_names[i]);
|
|
if (p_omitMissing
|
|
&& !QFileInfo::exists(filePath)) {
|
|
// The attachment file does not exist. We skip it to avoid error.
|
|
continue;
|
|
}
|
|
|
|
if (!VUtils::deleteFile(getNotebook(), filePath, false)) {
|
|
ret = false;
|
|
qWarning() << "fail to delete attachment" << p_names[i]
|
|
<< "for note" << m_name;
|
|
}
|
|
}
|
|
|
|
// Delete the attachment folder if m_attachments is empty now.
|
|
if (m_attachments.isEmpty()) {
|
|
dir.cdUp();
|
|
if (!dir.rmdir(m_attachmentFolder)) {
|
|
ret = false;
|
|
qWarning() << "fail to delete attachment folder" << m_attachmentFolder
|
|
<< "for note" << m_name;
|
|
}
|
|
}
|
|
|
|
if (!getDirectory()->updateFileConfig(this)) {
|
|
ret = false;
|
|
qWarning() << "fail to update config of file" << m_name
|
|
<< "in directory" << fetchBasePath();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int VNoteFile::findAttachment(const QString &p_name, bool p_caseSensitive)
|
|
{
|
|
const QString name = p_caseSensitive ? p_name : p_name.toLower();
|
|
for (int i = 0; i < m_attachments.size(); ++i) {
|
|
QString attaName = p_caseSensitive ? m_attachments[i].m_name
|
|
: m_attachments[i].m_name.toLower();
|
|
if (name == attaName) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool VNoteFile::sortAttachments(const QVector<int> &p_sortedIdx)
|
|
{
|
|
V_ASSERT(m_opened);
|
|
V_ASSERT(p_sortedIdx.size() == m_attachments.size());
|
|
|
|
auto ori = m_attachments;
|
|
|
|
for (int i = 0; i < p_sortedIdx.size(); ++i) {
|
|
m_attachments[i] = ori[p_sortedIdx[i]];
|
|
}
|
|
|
|
bool ret = true;
|
|
if (!getDirectory()->updateFileConfig(this)) {
|
|
qWarning() << "fail to reorder attachments in config" << p_sortedIdx;
|
|
m_attachments = ori;
|
|
ret = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool VNoteFile::renameAttachment(const QString &p_oldName, const QString &p_newName)
|
|
{
|
|
int idx = findAttachment(p_oldName);
|
|
if (idx == -1) {
|
|
return false;
|
|
}
|
|
|
|
QDir dir(fetchAttachmentFolderPath());
|
|
if (!dir.rename(p_oldName, p_newName)) {
|
|
qWarning() << "fail to rename attachment file" << p_oldName << p_newName;
|
|
return false;
|
|
}
|
|
|
|
m_attachments[idx].m_name = p_newName;
|
|
|
|
if (!getDirectory()->updateFileConfig(this)) {
|
|
qWarning() << "fail to rename attachment in config" << p_oldName << p_newName;
|
|
|
|
m_attachments[idx].m_name = p_oldName;
|
|
dir.rename(p_newName, p_oldName);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QVector<QString> VNoteFile::checkAttachments()
|
|
{
|
|
QVector<QString> missing;
|
|
|
|
QDir dir(fetchAttachmentFolderPath());
|
|
for (auto const & atta : m_attachments) {
|
|
QString file = dir.filePath(atta.m_name);
|
|
if (!QFileInfo::exists(file)) {
|
|
missing.push_back(atta.m_name);
|
|
}
|
|
}
|
|
|
|
return missing;
|
|
}
|
|
|
|
bool VNoteFile::deleteFile(VNoteFile *p_file, QString *p_errMsg)
|
|
{
|
|
Q_ASSERT(!p_file->isOpened());
|
|
|
|
bool ret = true;
|
|
QString name = p_file->getName();
|
|
QString path = p_file->fetchPath();
|
|
|
|
if (!p_file->deleteFile(p_errMsg)) {
|
|
qWarning() << "fail to delete file" << name << path;
|
|
ret = false;
|
|
}
|
|
|
|
VDirectory *dir = p_file->getDirectory();
|
|
Q_ASSERT(dir);
|
|
if (!dir->removeFile(p_file)) {
|
|
qWarning() << "fail to remove file from directory" << name << path;
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to remove the note from the folder configuration."));
|
|
ret = false;
|
|
}
|
|
|
|
delete p_file;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool VNoteFile::copyFile(VDirectory *p_destDir,
|
|
const QString &p_destName,
|
|
VNoteFile *p_file,
|
|
bool p_isCut,
|
|
VNoteFile **p_targetFile,
|
|
QString *p_errMsg)
|
|
{
|
|
bool ret = true;
|
|
*p_targetFile = NULL;
|
|
int nrImageCopied = 0;
|
|
bool attachmentFolderCopied = false;
|
|
|
|
QString srcPath = QDir::cleanPath(p_file->fetchPath());
|
|
QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
|
|
if (VUtils::equalPath(srcPath, destPath)) {
|
|
*p_targetFile = p_file;
|
|
return false;
|
|
}
|
|
|
|
if (!p_destDir->isOpened()) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to open target folder."));
|
|
return false;
|
|
}
|
|
|
|
QString opStr = p_isCut ? tr("cut") : tr("copy");
|
|
VDirectory *srcDir = p_file->getDirectory();
|
|
DocType docType = p_file->getDocType();
|
|
|
|
Q_ASSERT(srcDir->isOpened());
|
|
Q_ASSERT(docType == VUtils::docTypeFromName(p_destName));
|
|
|
|
// Images to be copied.
|
|
QVector<ImageLink> images;
|
|
if (docType == DocType::Markdown) {
|
|
images = VUtils::fetchImagesFromMarkdownFile(p_file,
|
|
ImageLink::LocalRelativeInternal);
|
|
}
|
|
|
|
// Attachments to be copied.
|
|
QString attaFolder = p_file->getAttachmentFolder();
|
|
QString attaFolderPath;
|
|
if (!attaFolder.isEmpty()) {
|
|
attaFolderPath = p_file->fetchAttachmentFolderPath();
|
|
}
|
|
|
|
// Copy the note file.
|
|
if (!VUtils::copyFile(srcPath, destPath, p_isCut)) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to %1 the note file.").arg(opStr));
|
|
qWarning() << "fail to" << opStr << "the note file" << srcPath << "to" << destPath;
|
|
return false;
|
|
}
|
|
|
|
// Add file to VDirectory.
|
|
VNoteFile *destFile = NULL;
|
|
if (p_isCut) {
|
|
srcDir->removeFile(p_file);
|
|
p_file->setName(p_destName);
|
|
if (p_destDir->addFile(p_file, -1)) {
|
|
destFile = p_file;
|
|
} else {
|
|
destFile = NULL;
|
|
}
|
|
} else {
|
|
destFile = p_destDir->addFile(p_destName, -1);
|
|
}
|
|
|
|
if (!destFile) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to add the note to target folder's configuration."));
|
|
return false;
|
|
}
|
|
|
|
// Copy images.
|
|
if (!copyInternalImages(images,
|
|
destFile->fetchBasePath(),
|
|
p_isCut,
|
|
&nrImageCopied,
|
|
p_errMsg)) {
|
|
ret = false;
|
|
}
|
|
|
|
// Copy attachment folder.
|
|
if (!attaFolderPath.isEmpty()) {
|
|
QDir dir(destFile->fetchBasePath());
|
|
QString folderPath = dir.filePath(destFile->getNotebook()->getAttachmentFolder());
|
|
attaFolder = VUtils::getDirNameWithSequence(folderPath, attaFolder);
|
|
folderPath = QDir(folderPath).filePath(attaFolder);
|
|
|
|
// Copy attaFolderPath to folderPath.
|
|
if (!VUtils::copyDirectory(attaFolderPath, folderPath, p_isCut)) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to %1 attachments folder %2 to %3. "
|
|
"Please manually maintain it.")
|
|
.arg(opStr).arg(attaFolderPath).arg(folderPath));
|
|
QVector<VAttachment> emptyAttas;
|
|
destFile->setAttachments(emptyAttas);
|
|
ret = false;
|
|
} else {
|
|
attachmentFolderCopied = true;
|
|
|
|
destFile->setAttachmentFolder(attaFolder);
|
|
if (!p_isCut) {
|
|
destFile->setAttachments(p_file->getAttachments());
|
|
}
|
|
}
|
|
|
|
if (!p_destDir->updateFileConfig(destFile)) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to update configuration of note %1.")
|
|
.arg(destFile->fetchPath()));
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
qDebug() << "copyFile:" << p_file << "to" << destFile
|
|
<< "copied_images:" << nrImageCopied
|
|
<< "copied_attachments:" << attachmentFolderCopied;
|
|
|
|
*p_targetFile = destFile;
|
|
return ret;
|
|
}
|
|
|
|
bool VNoteFile::copyInternalImages(const QVector<ImageLink> &p_images,
|
|
const QString &p_destDirPath,
|
|
bool p_isCut,
|
|
int *p_nrImageCopied,
|
|
QString *p_errMsg)
|
|
{
|
|
bool ret = true;
|
|
QDir parentDir(p_destDirPath);
|
|
QSet<QString> processedImages;
|
|
QString opStr = p_isCut ? tr("cut") : tr("copy");
|
|
int nrImageCopied = 0;
|
|
for (int i = 0; i < p_images.size(); ++i) {
|
|
const ImageLink &link = p_images[i];
|
|
if (processedImages.contains(link.m_path)) {
|
|
continue;
|
|
}
|
|
|
|
processedImages.insert(link.m_path);
|
|
|
|
if (!QFileInfo::exists(link.m_path)) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Source image %1 does not exist.")
|
|
.arg(link.m_path));
|
|
ret = false;
|
|
continue;
|
|
}
|
|
|
|
QString imageFolder = VUtils::directoryNameFromPath(VUtils::basePathFromPath(link.m_path));
|
|
QString destImagePath = QDir(parentDir.filePath(imageFolder)).filePath(VUtils::fileNameFromPath(link.m_path));
|
|
|
|
if (VUtils::equalPath(link.m_path, destImagePath)) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Skip image with the same source and target path %1.")
|
|
.arg(link.m_path));
|
|
ret = false;
|
|
continue;
|
|
}
|
|
|
|
if (!VUtils::copyFile(link.m_path, destImagePath, p_isCut)) {
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to %1 image %2 to %3. "
|
|
"Please manually %1 it and modify the note.")
|
|
.arg(opStr).arg(link.m_path).arg(destImagePath));
|
|
ret = false;
|
|
} else {
|
|
++nrImageCopied;
|
|
qDebug() << opStr << "image" << link.m_path << "to" << destImagePath;
|
|
}
|
|
}
|
|
|
|
*p_nrImageCopied = nrImageCopied;
|
|
return ret;
|
|
}
|
|
|
|
void VNoteFile::removeTag(const QString &p_tag)
|
|
{
|
|
if (p_tag.isEmpty() || m_tags.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
int nr = m_tags.removeAll(p_tag);
|
|
if (nr > 0) {
|
|
if (!getDirectory()->updateFileConfig(this)) {
|
|
qWarning() << "fail to update config of file" << m_name
|
|
<< "in directory" << fetchBasePath();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VNoteFile::addTag(const QString &p_tag)
|
|
{
|
|
Q_ASSERT(isOpened());
|
|
|
|
if (p_tag.isEmpty() || hasTag(p_tag)) {
|
|
return false;
|
|
}
|
|
|
|
m_tags.append(p_tag);
|
|
if (!getDirectory()->updateFileConfig(this)) {
|
|
qWarning() << "fail to update config of file" << m_name
|
|
<< "in directory" << fetchBasePath();
|
|
m_tags.removeAll(p_tag);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|