vnotefile.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. #include "vnotefile.h"
  2. #include <QDir>
  3. #include <QDebug>
  4. #include <QTextEdit>
  5. #include <QFileInfo>
  6. #include <QJsonObject>
  7. #include <QJsonArray>
  8. #include <QDebug>
  9. #include "utils/vutils.h"
  10. #include "vdirectory.h"
  11. VNoteFile::VNoteFile(VDirectory *p_directory,
  12. const QString &p_name,
  13. FileType p_type,
  14. bool p_modifiable,
  15. QDateTime p_createdTimeUtc,
  16. QDateTime p_modifiedTimeUtc,
  17. const QString &p_attachmentFolder,
  18. const QVector<VAttachment> &p_attachments)
  19. : VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc),
  20. m_attachmentFolder(p_attachmentFolder),
  21. m_attachments(p_attachments)
  22. {
  23. }
  24. QString VNoteFile::fetchPath() const
  25. {
  26. return QDir(getDirectory()->fetchPath()).filePath(m_name);
  27. }
  28. QString VNoteFile::fetchBasePath() const
  29. {
  30. return getDirectory()->fetchPath();
  31. }
  32. QString VNoteFile::fetchImageFolderPath() const
  33. {
  34. return QDir(fetchBasePath()).filePath(getNotebook()->getImageFolder());
  35. }
  36. bool VNoteFile::useRelativeImageFolder() const
  37. {
  38. // Always use relative image folder.
  39. return true;
  40. }
  41. QString VNoteFile::getImageFolderInLink() const
  42. {
  43. return getNotebook()->getImageFolder();
  44. }
  45. void VNoteFile::setName(const QString &p_name)
  46. {
  47. m_name = p_name;
  48. }
  49. bool VNoteFile::rename(const QString &p_name)
  50. {
  51. if (m_name == p_name) {
  52. return true;
  53. }
  54. QString oldName = m_name;
  55. VDirectory *dir = getDirectory();
  56. Q_ASSERT(dir);
  57. // Rename it in disk.
  58. QDir diskDir(dir->fetchPath());
  59. if (!diskDir.rename(m_name, p_name)) {
  60. qWarning() << "fail to rename file" << m_name << "to" << p_name << "in disk";
  61. return false;
  62. }
  63. m_name = p_name;
  64. // Update parent directory's config file.
  65. if (!dir->updateFileConfig(this)) {
  66. m_name = oldName;
  67. diskDir.rename(p_name, m_name);
  68. return false;
  69. }
  70. // Can't not change doc type.
  71. Q_ASSERT(m_docType == DocType::Unknown
  72. || m_docType == VUtils::docTypeFromName(m_name));
  73. m_docType = VUtils::docTypeFromName(m_name);
  74. qDebug() << "file renamed from" << oldName << "to" << m_name;
  75. return true;
  76. }
  77. VDirectory *VNoteFile::getDirectory()
  78. {
  79. Q_ASSERT(parent());
  80. return (VDirectory *)parent();
  81. }
  82. const VDirectory *VNoteFile::getDirectory() const
  83. {
  84. Q_ASSERT(parent());
  85. return (const VDirectory *)parent();
  86. }
  87. VNotebook *VNoteFile::getNotebook()
  88. {
  89. return getDirectory()->getNotebook();
  90. }
  91. const VNotebook *VNoteFile::getNotebook() const
  92. {
  93. return getDirectory()->getNotebook();
  94. }
  95. QString VNoteFile::getNotebookName() const
  96. {
  97. return getDirectory()->getNotebookName();
  98. }
  99. QString VNoteFile::fetchRelativePath() const
  100. {
  101. return QDir(getDirectory()->fetchRelativePath()).filePath(m_name);
  102. }
  103. VNoteFile *VNoteFile::fromJson(VDirectory *p_directory,
  104. const QJsonObject &p_json,
  105. FileType p_type,
  106. bool p_modifiable)
  107. {
  108. // Attachments.
  109. QJsonArray attachmentJson = p_json[DirConfig::c_attachments].toArray();
  110. QVector<VAttachment> attachments;
  111. for (int i = 0; i < attachmentJson.size(); ++i) {
  112. QJsonObject attachmentItem = attachmentJson[i].toObject();
  113. attachments.push_back(VAttachment(attachmentItem[DirConfig::c_name].toString()));
  114. }
  115. return new VNoteFile(p_directory,
  116. p_json[DirConfig::c_name].toString(),
  117. p_type,
  118. p_modifiable,
  119. QDateTime::fromString(p_json[DirConfig::c_createdTime].toString(),
  120. Qt::ISODate),
  121. QDateTime::fromString(p_json[DirConfig::c_modifiedTime].toString(),
  122. Qt::ISODate),
  123. p_json[DirConfig::c_attachmentFolder].toString(),
  124. attachments);
  125. }
  126. QJsonObject VNoteFile::toConfigJson() const
  127. {
  128. QJsonObject item;
  129. item[DirConfig::c_name] = m_name;
  130. item[DirConfig::c_createdTime] = m_createdTimeUtc.toString(Qt::ISODate);
  131. item[DirConfig::c_modifiedTime] = m_modifiedTimeUtc.toString(Qt::ISODate);
  132. item[DirConfig::c_attachmentFolder] = m_attachmentFolder;
  133. // Attachments.
  134. QJsonArray attachmentJson;
  135. for (int i = 0; i < m_attachments.size(); ++i) {
  136. const VAttachment &item = m_attachments[i];
  137. QJsonObject attachmentItem;
  138. attachmentItem[DirConfig::c_name] = item.m_name;
  139. attachmentJson.append(attachmentItem);
  140. }
  141. item[DirConfig::c_attachments] = attachmentJson;
  142. return item;
  143. }
  144. bool VNoteFile::deleteFile(QString *p_errMsg)
  145. {
  146. Q_ASSERT(!m_opened);
  147. Q_ASSERT(parent());
  148. bool ret = true;
  149. // Delete local images if it is Markdown.
  150. if (m_docType == DocType::Markdown) {
  151. if (!deleteInternalImages()) {
  152. ret = false;
  153. VUtils::addErrMsg(p_errMsg, tr("Fail to delete images of this note."));
  154. }
  155. }
  156. // Delete attachments.
  157. if (!deleteAttachments()) {
  158. ret = false;
  159. VUtils::addErrMsg(p_errMsg, tr("Fail to delete attachments of this note."));
  160. }
  161. // Delete the file.
  162. QString filePath = fetchPath();
  163. if (VUtils::deleteFile(getNotebook(), filePath, false)) {
  164. qDebug() << "deleted" << m_name << filePath;
  165. } else {
  166. ret = false;
  167. VUtils::addErrMsg(p_errMsg, tr("Fail to delete the note file."));
  168. qWarning() << "fail to delete" << m_name << filePath;
  169. }
  170. return ret;
  171. }
  172. bool VNoteFile::deleteInternalImages()
  173. {
  174. Q_ASSERT(parent() && m_docType == DocType::Markdown);
  175. QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(this,
  176. ImageLink::LocalRelativeInternal);
  177. int deleted = 0;
  178. for (int i = 0; i < images.size(); ++i) {
  179. if (VUtils::deleteFile(getNotebook(), images[i].m_path, false)) {
  180. ++deleted;
  181. }
  182. }
  183. qDebug() << "delete" << deleted << "images for" << m_name << fetchPath();
  184. return deleted == images.size();
  185. }
  186. bool VNoteFile::addAttachment(const QString &p_file)
  187. {
  188. if (p_file.isEmpty() || !QFileInfo::exists(p_file)) {
  189. return false;
  190. }
  191. QString folderPath = fetchAttachmentFolderPath();
  192. QString name = VUtils::fileNameFromPath(p_file);
  193. Q_ASSERT(!name.isEmpty());
  194. name = VUtils::getFileNameWithSequence(folderPath, name);
  195. QString destPath = QDir(folderPath).filePath(name);
  196. if (!VUtils::copyFile(p_file, destPath, false)) {
  197. return false;
  198. }
  199. m_attachments.push_back(VAttachment(name));
  200. if (!getDirectory()->updateFileConfig(this)) {
  201. qWarning() << "fail to update config of file" << m_name
  202. << "in directory" << fetchBasePath();
  203. return false;
  204. }
  205. return true;
  206. }
  207. QString VNoteFile::fetchAttachmentFolderPath()
  208. {
  209. QString folderPath = QDir(fetchBasePath()).filePath(getNotebook()->getAttachmentFolder());
  210. if (m_attachmentFolder.isEmpty()) {
  211. m_attachmentFolder = VUtils::getRandomFileName(folderPath);
  212. }
  213. folderPath = QDir(folderPath).filePath(m_attachmentFolder);
  214. if (!QFileInfo::exists(folderPath)) {
  215. QDir dir;
  216. if (!dir.mkpath(folderPath)) {
  217. qWarning() << "fail to create attachment folder of notebook" << m_name << folderPath;
  218. }
  219. }
  220. return folderPath;
  221. }
  222. bool VNoteFile::deleteAttachments(bool p_omitMissing)
  223. {
  224. if (m_attachments.isEmpty()) {
  225. return true;
  226. }
  227. QVector<QString> attas;
  228. for (int i = 0; i < m_attachments.size(); ++i) {
  229. attas.push_back(m_attachments[i].m_name);
  230. }
  231. return deleteAttachments(attas, p_omitMissing);
  232. }
  233. bool VNoteFile::deleteAttachments(const QVector<QString> &p_names,
  234. bool p_omitMissing)
  235. {
  236. if (p_names.isEmpty()) {
  237. return true;
  238. }
  239. QDir dir(fetchAttachmentFolderPath());
  240. bool ret = true;
  241. for (int i = 0; i < p_names.size(); ++i) {
  242. int idx = findAttachment(p_names[i]);
  243. if (idx == -1) {
  244. ret = false;
  245. continue;
  246. }
  247. m_attachments.remove(idx);
  248. QString filePath = dir.filePath(p_names[i]);
  249. if (p_omitMissing
  250. && !QFileInfo::exists(filePath)) {
  251. // The attachment file does not exist. We skip it to avoid error.
  252. continue;
  253. }
  254. if (!VUtils::deleteFile(getNotebook(), filePath, false)) {
  255. ret = false;
  256. qWarning() << "fail to delete attachment" << p_names[i]
  257. << "for note" << m_name;
  258. }
  259. }
  260. // Delete the attachment folder if m_attachments is empty now.
  261. if (m_attachments.isEmpty()) {
  262. dir.cdUp();
  263. if (!dir.rmdir(m_attachmentFolder)) {
  264. ret = false;
  265. qWarning() << "fail to delete attachment folder" << m_attachmentFolder
  266. << "for note" << m_name;
  267. }
  268. }
  269. if (!getDirectory()->updateFileConfig(this)) {
  270. ret = false;
  271. qWarning() << "fail to update config of file" << m_name
  272. << "in directory" << fetchBasePath();
  273. }
  274. return ret;
  275. }
  276. int VNoteFile::findAttachment(const QString &p_name, bool p_caseSensitive)
  277. {
  278. const QString name = p_caseSensitive ? p_name : p_name.toLower();
  279. for (int i = 0; i < m_attachments.size(); ++i) {
  280. QString attaName = p_caseSensitive ? m_attachments[i].m_name
  281. : m_attachments[i].m_name.toLower();
  282. if (name == attaName) {
  283. return i;
  284. }
  285. }
  286. return -1;
  287. }
  288. bool VNoteFile::sortAttachments(const QVector<int> &p_sortedIdx)
  289. {
  290. V_ASSERT(m_opened);
  291. V_ASSERT(p_sortedIdx.size() == m_attachments.size());
  292. auto ori = m_attachments;
  293. for (int i = 0; i < p_sortedIdx.size(); ++i) {
  294. m_attachments[i] = ori[p_sortedIdx[i]];
  295. }
  296. bool ret = true;
  297. if (!getDirectory()->updateFileConfig(this)) {
  298. qWarning() << "fail to reorder attachments in config" << p_sortedIdx;
  299. m_attachments = ori;
  300. ret = false;
  301. }
  302. return ret;
  303. }
  304. bool VNoteFile::renameAttachment(const QString &p_oldName, const QString &p_newName)
  305. {
  306. int idx = findAttachment(p_oldName);
  307. if (idx == -1) {
  308. return false;
  309. }
  310. QDir dir(fetchAttachmentFolderPath());
  311. if (!dir.rename(p_oldName, p_newName)) {
  312. qWarning() << "fail to rename attachment file" << p_oldName << p_newName;
  313. return false;
  314. }
  315. m_attachments[idx].m_name = p_newName;
  316. if (!getDirectory()->updateFileConfig(this)) {
  317. qWarning() << "fail to rename attachment in config" << p_oldName << p_newName;
  318. m_attachments[idx].m_name = p_oldName;
  319. dir.rename(p_newName, p_oldName);
  320. return false;
  321. }
  322. return true;
  323. }
  324. QVector<QString> VNoteFile::checkAttachments()
  325. {
  326. QVector<QString> missing;
  327. QDir dir(fetchAttachmentFolderPath());
  328. for (auto const & atta : m_attachments) {
  329. QString file = dir.filePath(atta.m_name);
  330. if (!QFileInfo::exists(file)) {
  331. missing.push_back(atta.m_name);
  332. }
  333. }
  334. return missing;
  335. }
  336. bool VNoteFile::deleteFile(VNoteFile *p_file, QString *p_errMsg)
  337. {
  338. Q_ASSERT(!p_file->isOpened());
  339. bool ret = true;
  340. QString name = p_file->getName();
  341. QString path = p_file->fetchPath();
  342. if (!p_file->deleteFile(p_errMsg)) {
  343. qWarning() << "fail to delete file" << name << path;
  344. ret = false;
  345. }
  346. VDirectory *dir = p_file->getDirectory();
  347. Q_ASSERT(dir);
  348. if (!dir->removeFile(p_file)) {
  349. qWarning() << "fail to remove file from directory" << name << path;
  350. VUtils::addErrMsg(p_errMsg, tr("Fail to remove the note from the folder configuration."));
  351. ret = false;
  352. }
  353. delete p_file;
  354. return ret;
  355. }
  356. bool VNoteFile::copyFile(VDirectory *p_destDir,
  357. const QString &p_destName,
  358. VNoteFile *p_file,
  359. bool p_isCut,
  360. VNoteFile **p_targetFile,
  361. QString *p_errMsg)
  362. {
  363. bool ret = true;
  364. *p_targetFile = NULL;
  365. int nrImageCopied = 0;
  366. bool attachmentFolderCopied = false;
  367. QString srcPath = QDir::cleanPath(p_file->fetchPath());
  368. QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
  369. if (VUtils::equalPath(srcPath, destPath)) {
  370. *p_targetFile = p_file;
  371. return false;
  372. }
  373. if (!p_destDir->isOpened()) {
  374. VUtils::addErrMsg(p_errMsg, tr("Fail to open target folder."));
  375. return false;
  376. }
  377. QString opStr = p_isCut ? tr("cut") : tr("copy");
  378. VDirectory *srcDir = p_file->getDirectory();
  379. DocType docType = p_file->getDocType();
  380. Q_ASSERT(srcDir->isOpened());
  381. Q_ASSERT(docType == VUtils::docTypeFromName(p_destName));
  382. // Images to be copied.
  383. QVector<ImageLink> images;
  384. if (docType == DocType::Markdown) {
  385. images = VUtils::fetchImagesFromMarkdownFile(p_file,
  386. ImageLink::LocalRelativeInternal);
  387. }
  388. // Attachments to be copied.
  389. QString attaFolder = p_file->getAttachmentFolder();
  390. QString attaFolderPath;
  391. if (!attaFolder.isEmpty()) {
  392. attaFolderPath = p_file->fetchAttachmentFolderPath();
  393. }
  394. // Copy the note file.
  395. if (!VUtils::copyFile(srcPath, destPath, p_isCut)) {
  396. VUtils::addErrMsg(p_errMsg, tr("Fail to %1 the note file.").arg(opStr));
  397. qWarning() << "fail to" << opStr << "the note file" << srcPath << "to" << destPath;
  398. return false;
  399. }
  400. // Add file to VDirectory.
  401. VNoteFile *destFile = NULL;
  402. if (p_isCut) {
  403. srcDir->removeFile(p_file);
  404. p_file->setName(p_destName);
  405. if (p_destDir->addFile(p_file, -1)) {
  406. destFile = p_file;
  407. } else {
  408. destFile = NULL;
  409. }
  410. } else {
  411. destFile = p_destDir->addFile(p_destName, -1);
  412. }
  413. if (!destFile) {
  414. VUtils::addErrMsg(p_errMsg, tr("Fail to add the note to target folder's configuration."));
  415. return false;
  416. }
  417. // Copy images.
  418. QDir parentDir(destFile->fetchBasePath());
  419. for (int i = 0; i < images.size(); ++i) {
  420. const ImageLink &link = images[i];
  421. if (!QFileInfo::exists(link.m_path)) {
  422. VUtils::addErrMsg(p_errMsg, tr("Source image %1 does not exist.")
  423. .arg(link.m_path));
  424. ret = false;
  425. continue;
  426. }
  427. QString imageFolder = VUtils::directoryNameFromPath(VUtils::basePathFromPath(link.m_path));
  428. QString destImagePath = QDir(parentDir.filePath(imageFolder)).filePath(VUtils::fileNameFromPath(link.m_path));
  429. if (VUtils::equalPath(link.m_path, destImagePath)) {
  430. VUtils::addErrMsg(p_errMsg, tr("Skip image with the same source and target path %1.")
  431. .arg(link.m_path));
  432. ret = false;
  433. continue;
  434. }
  435. if (!VUtils::copyFile(link.m_path, destImagePath, p_isCut)) {
  436. VUtils::addErrMsg(p_errMsg, tr("Fail to %1 image %2 to %3. "
  437. "Please manually %1 it and modify the note.")
  438. .arg(opStr).arg(link.m_path).arg(destImagePath));
  439. ret = false;
  440. } else {
  441. ++nrImageCopied;
  442. qDebug() << opStr << "image" << link.m_path << "to" << destImagePath;
  443. }
  444. }
  445. // Copy attachment folder.
  446. if (!attaFolderPath.isEmpty()) {
  447. QDir dir(destFile->fetchBasePath());
  448. QString folderPath = dir.filePath(destFile->getNotebook()->getAttachmentFolder());
  449. attaFolder = VUtils::getFileNameWithSequence(folderPath, attaFolder);
  450. folderPath = QDir(folderPath).filePath(attaFolder);
  451. // Copy attaFolderPath to folderPath.
  452. if (!VUtils::copyDirectory(attaFolderPath, folderPath, p_isCut)) {
  453. VUtils::addErrMsg(p_errMsg, tr("Fail to %1 attachments folder %2 to %3. "
  454. "Please manually maintain it.")
  455. .arg(opStr).arg(attaFolderPath).arg(folderPath));
  456. QVector<VAttachment> emptyAttas;
  457. destFile->setAttachments(emptyAttas);
  458. ret = false;
  459. } else {
  460. attachmentFolderCopied = true;
  461. destFile->setAttachmentFolder(attaFolder);
  462. if (!p_isCut) {
  463. destFile->setAttachments(p_file->getAttachments());
  464. }
  465. }
  466. if (!p_destDir->updateFileConfig(destFile)) {
  467. VUtils::addErrMsg(p_errMsg, tr("Fail to update configuration of note %1.")
  468. .arg(destFile->fetchPath()));
  469. ret = false;
  470. }
  471. }
  472. qDebug() << "copyFile:" << p_file << "to" << destFile
  473. << "copied_images:" << nrImageCopied
  474. << "copied_attachments:" << attachmentFolderCopied;
  475. *p_targetFile = destFile;
  476. return ret;
  477. }