vdirectory.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. #include "vdirectory.h"
  2. #include <QDir>
  3. #include <QJsonObject>
  4. #include <QJsonArray>
  5. #include <QDebug>
  6. #include "vconfigmanager.h"
  7. #include "vfile.h"
  8. #include "utils/vutils.h"
  9. extern VConfigManager *g_config;
  10. VDirectory::VDirectory(VNotebook *p_notebook,
  11. const QString &p_name, QObject *p_parent)
  12. : QObject(p_parent), m_notebook(p_notebook), m_name(p_name), m_opened(false),
  13. m_expanded(false)
  14. {
  15. }
  16. bool VDirectory::open()
  17. {
  18. if (m_opened) {
  19. return true;
  20. }
  21. V_ASSERT(m_subDirs.isEmpty() && m_files.isEmpty());
  22. QString path = retrivePath();
  23. QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
  24. if (configJson.isEmpty()) {
  25. qWarning() << "invalid directory configuration in path" << path;
  26. return false;
  27. }
  28. // [sub_directories] section
  29. QJsonArray dirJson = configJson[DirConfig::c_subDirectories].toArray();
  30. for (int i = 0; i < dirJson.size(); ++i) {
  31. QJsonObject dirItem = dirJson[i].toObject();
  32. VDirectory *dir = new VDirectory(m_notebook, dirItem[DirConfig::c_name].toString(), this);
  33. m_subDirs.append(dir);
  34. }
  35. // [files] section
  36. QJsonArray fileJson = configJson[DirConfig::c_files].toArray();
  37. for (int i = 0; i < fileJson.size(); ++i) {
  38. QJsonObject fileItem = fileJson[i].toObject();
  39. VFile *file = new VFile(fileItem[DirConfig::c_name].toString(), this);
  40. m_files.append(file);
  41. }
  42. m_opened = true;
  43. return true;
  44. }
  45. void VDirectory::close()
  46. {
  47. if (!m_opened) {
  48. return;
  49. }
  50. for (int i = 0; i < m_subDirs.size(); ++i) {
  51. VDirectory *dir = m_subDirs[i];
  52. dir->close();
  53. delete dir;
  54. }
  55. m_subDirs.clear();
  56. for (int i = 0; i < m_files.size(); ++i) {
  57. VFile *file = m_files[i];
  58. file->close();
  59. delete file;
  60. }
  61. m_files.clear();
  62. m_opened = false;
  63. }
  64. QString VDirectory::retriveBasePath() const
  65. {
  66. return VUtils::basePathFromPath(retrivePath());
  67. }
  68. QString VDirectory::retrivePath(const VDirectory *p_dir) const
  69. {
  70. if (!p_dir) {
  71. return "";
  72. }
  73. VDirectory *parentDir = (VDirectory *)p_dir->parent();
  74. if (parentDir) {
  75. // Not the root directory
  76. return QDir(retrivePath(parentDir)).filePath(p_dir->getName());
  77. } else {
  78. return m_notebook->getPath();
  79. }
  80. }
  81. QString VDirectory::retriveRelativePath(const VDirectory *p_dir) const
  82. {
  83. if (!p_dir) {
  84. return "";
  85. }
  86. VDirectory *parentDir = (VDirectory *)p_dir->parent();
  87. if (parentDir) {
  88. // Not the root directory
  89. return QDir(retriveRelativePath(parentDir)).filePath(p_dir->getName());
  90. } else {
  91. return "";
  92. }
  93. }
  94. QJsonObject VDirectory::toConfigJson() const
  95. {
  96. QJsonObject dirJson;
  97. dirJson[DirConfig::c_version] = "1";
  98. QJsonArray subDirs;
  99. for (int i = 0; i < m_subDirs.size(); ++i) {
  100. QJsonObject item;
  101. item[DirConfig::c_name] = m_subDirs[i]->getName();
  102. subDirs.append(item);
  103. }
  104. dirJson[DirConfig::c_subDirectories] = subDirs;
  105. QJsonArray files;
  106. for (int i = 0; i < m_files.size(); ++i) {
  107. QJsonObject item;
  108. item[DirConfig::c_name] = m_files[i]->getName();
  109. files.append(item);
  110. }
  111. dirJson[DirConfig::c_files] = files;
  112. return dirJson;
  113. }
  114. bool VDirectory::readConfig()
  115. {
  116. return true;
  117. }
  118. bool VDirectory::writeToConfig() const
  119. {
  120. QJsonObject json = toConfigJson();
  121. if (!getParentDirectory()) {
  122. // Root directory.
  123. addNotebookConfig(json);
  124. }
  125. qDebug() << "folder" << m_name << "write to config" << json;
  126. return writeToConfig(json);
  127. }
  128. bool VDirectory::writeToConfig(const QJsonObject &p_json) const
  129. {
  130. return VConfigManager::writeDirectoryConfig(retrivePath(), p_json);
  131. }
  132. void VDirectory::addNotebookConfig(QJsonObject &p_json) const
  133. {
  134. V_ASSERT(!getParentDirectory());
  135. QJsonObject nbJson = m_notebook->toConfigJsonNotebook();
  136. // Merge it to p_json.
  137. for (auto it = nbJson.begin(); it != nbJson.end(); ++it) {
  138. V_ASSERT(!p_json.contains(it.key()));
  139. p_json[it.key()] = it.value();
  140. }
  141. }
  142. VDirectory *VDirectory::createSubDirectory(const QString &p_name)
  143. {
  144. Q_ASSERT(!p_name.isEmpty());
  145. // First open current directory
  146. if (!open()) {
  147. return NULL;
  148. }
  149. qDebug() << "create subfolder" << p_name << "in" << m_name;
  150. QString path = retrivePath();
  151. QDir dir(path);
  152. if (!dir.mkdir(p_name)) {
  153. qWarning() << "fail to create directory" << p_name << "under" << path;
  154. return NULL;
  155. }
  156. VDirectory *ret = new VDirectory(m_notebook, p_name, this);
  157. if (!ret->writeToConfig()) {
  158. dir.rmdir(p_name);
  159. delete ret;
  160. return NULL;
  161. }
  162. m_subDirs.append(ret);
  163. if (!writeToConfig()) {
  164. VConfigManager::deleteDirectoryConfig(QDir(path).filePath(p_name));
  165. dir.rmdir(p_name);
  166. delete ret;
  167. m_subDirs.removeLast();
  168. return NULL;
  169. }
  170. return ret;
  171. }
  172. VDirectory *VDirectory::findSubDirectory(const QString &p_name, bool p_caseSensitive)
  173. {
  174. if (!open()) {
  175. return NULL;
  176. }
  177. QString name = p_caseSensitive ? p_name : p_name.toLower();
  178. for (int i = 0; i < m_subDirs.size(); ++i) {
  179. if (name == (p_caseSensitive ? m_subDirs[i]->getName() : m_subDirs[i]->getName().toLower())) {
  180. return m_subDirs[i];
  181. }
  182. }
  183. return NULL;
  184. }
  185. VFile *VDirectory::findFile(const QString &p_name, bool p_caseSensitive)
  186. {
  187. if (!open()) {
  188. return NULL;
  189. }
  190. QString name = p_caseSensitive ? p_name : p_name.toLower();
  191. for (int i = 0; i < m_files.size(); ++i) {
  192. if (name == (p_caseSensitive ? m_files[i]->getName() : m_files[i]->getName().toLower())) {
  193. return m_files[i];
  194. }
  195. }
  196. return NULL;
  197. }
  198. bool VDirectory::containsFile(const VFile *p_file) const
  199. {
  200. if (!p_file) {
  201. return false;
  202. }
  203. QObject *paDir = p_file->parent();
  204. while (paDir) {
  205. if (paDir == this) {
  206. return true;
  207. } else {
  208. paDir = paDir->parent();
  209. }
  210. }
  211. return false;
  212. }
  213. VFile *VDirectory::createFile(const QString &p_name)
  214. {
  215. Q_ASSERT(!p_name.isEmpty());
  216. if (!open()) {
  217. return NULL;
  218. }
  219. QString path = retrivePath();
  220. QFile file(QDir(path).filePath(p_name));
  221. if (!file.open(QIODevice::WriteOnly)) {
  222. qWarning() << "fail to create file" << p_name;
  223. return NULL;
  224. }
  225. file.close();
  226. VFile *ret = new VFile(p_name, this);
  227. m_files.append(ret);
  228. if (!writeToConfig()) {
  229. file.remove();
  230. delete ret;
  231. m_files.removeLast();
  232. return NULL;
  233. }
  234. qDebug() << "note" << p_name << "created in folder" << m_name;
  235. return ret;
  236. }
  237. bool VDirectory::addFile(VFile *p_file, int p_index)
  238. {
  239. if (!open()) {
  240. return false;
  241. }
  242. if (p_index == -1) {
  243. m_files.append(p_file);
  244. } else {
  245. m_files.insert(p_index, p_file);
  246. }
  247. if (!writeToConfig()) {
  248. if (p_index == -1) {
  249. m_files.removeLast();
  250. } else {
  251. m_files.remove(p_index);
  252. }
  253. return false;
  254. }
  255. p_file->setParent(this);
  256. qDebug() << "note" << p_file->getName() << "added to folder" << m_name;
  257. return true;
  258. }
  259. VFile *VDirectory::addFile(const QString &p_name, int p_index)
  260. {
  261. if (!open() || p_name.isEmpty()) {
  262. return NULL;
  263. }
  264. VFile *file = new VFile(p_name, this);
  265. if (!file) {
  266. return NULL;
  267. }
  268. if (!addFile(file, p_index)) {
  269. delete file;
  270. return NULL;
  271. }
  272. return file;
  273. }
  274. bool VDirectory::addSubDirectory(VDirectory *p_dir, int p_index)
  275. {
  276. if (!open()) {
  277. return false;
  278. }
  279. if (p_index == -1) {
  280. m_subDirs.append(p_dir);
  281. } else {
  282. m_subDirs.insert(p_index, p_dir);
  283. }
  284. if (!writeToConfig()) {
  285. if (p_index == -1) {
  286. m_subDirs.removeLast();
  287. } else {
  288. m_subDirs.remove(p_index);
  289. }
  290. return false;
  291. }
  292. p_dir->setParent(this);
  293. qDebug() << "folder" << p_dir->getName() << "added to folder" << m_name;
  294. return true;
  295. }
  296. VDirectory *VDirectory::addSubDirectory(const QString &p_name, int p_index)
  297. {
  298. if (!open()) {
  299. return NULL;
  300. }
  301. VDirectory *dir = new VDirectory(m_notebook, p_name, this);
  302. if (!dir) {
  303. return NULL;
  304. }
  305. if (!addSubDirectory(dir, p_index)) {
  306. delete dir;
  307. return NULL;
  308. }
  309. return dir;
  310. }
  311. void VDirectory::deleteSubDirectory(VDirectory *p_subDir)
  312. {
  313. QString dirPath = p_subDir->retrivePath();
  314. p_subDir->close();
  315. removeSubDirectory(p_subDir);
  316. // Delete the entire directory
  317. QDir dir(dirPath);
  318. if (!dir.removeRecursively()) {
  319. qWarning() << "fail to remove directory" << dirPath << "recursively";
  320. } else {
  321. qDebug() << "deleted" << dirPath << "from disk";
  322. }
  323. delete p_subDir;
  324. }
  325. bool VDirectory::removeSubDirectory(VDirectory *p_dir)
  326. {
  327. V_ASSERT(m_opened);
  328. V_ASSERT(p_dir);
  329. int index = m_subDirs.indexOf(p_dir);
  330. V_ASSERT(index != -1);
  331. m_subDirs.remove(index);
  332. if (!writeToConfig()) {
  333. return false;
  334. }
  335. qDebug() << "folder" << p_dir->getName() << "removed from folder" << m_name;
  336. return true;
  337. }
  338. bool VDirectory::removeFile(VFile *p_file)
  339. {
  340. V_ASSERT(m_opened);
  341. V_ASSERT(p_file);
  342. int index = m_files.indexOf(p_file);
  343. V_ASSERT(index != -1);
  344. m_files.remove(index);
  345. if (!writeToConfig()) {
  346. return false;
  347. }
  348. qDebug() << "note" << p_file->getName() << "removed from folder" << m_name;
  349. return true;
  350. }
  351. void VDirectory::deleteFile(VFile *p_file)
  352. {
  353. removeFile(p_file);
  354. // Delete the file
  355. V_ASSERT(!p_file->isOpened());
  356. V_ASSERT(p_file->parent());
  357. p_file->deleteDiskFile();
  358. delete p_file;
  359. }
  360. bool VDirectory::rename(const QString &p_name)
  361. {
  362. if (m_name == p_name) {
  363. return true;
  364. }
  365. QString oldName = m_name;
  366. VDirectory *parentDir = getParentDirectory();
  367. V_ASSERT(parentDir);
  368. // Rename it in disk.
  369. QDir dir(parentDir->retrivePath());
  370. if (!dir.rename(m_name, p_name)) {
  371. qWarning() << "fail to rename folder" << m_name << "to" << p_name << "in disk";
  372. return false;
  373. }
  374. m_name = p_name;
  375. // Update parent's config file
  376. if (!parentDir->writeToConfig()) {
  377. m_name = oldName;
  378. dir.rename(p_name, m_name);
  379. return false;
  380. }
  381. qDebug() << "folder renamed from" << oldName << "to" << m_name;
  382. return true;
  383. }
  384. VFile *VDirectory::copyFile(VDirectory *p_destDir, const QString &p_destName,
  385. VFile *p_srcFile, bool p_cut)
  386. {
  387. QString srcPath = QDir::cleanPath(p_srcFile->retrivePath());
  388. QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName));
  389. if (VUtils::equalPath(srcPath, destPath)) {
  390. return p_srcFile;
  391. }
  392. VDirectory *srcDir = p_srcFile->getDirectory();
  393. DocType docType = p_srcFile->getDocType();
  394. DocType newDocType = VUtils::docTypeFromName(destPath);
  395. QVector<ImageLink> images;
  396. if (docType == DocType::Markdown) {
  397. images = VUtils::fetchImagesFromMarkdownFile(p_srcFile,
  398. ImageLink::LocalRelativeInternal);
  399. }
  400. // Copy the file
  401. if (!VUtils::copyFile(srcPath, destPath, p_cut)) {
  402. return NULL;
  403. }
  404. // Handle VDirectory and VFile
  405. int index = -1;
  406. VFile *destFile = NULL;
  407. if (p_cut) {
  408. // Remove the file from config
  409. srcDir->removeFile(p_srcFile);
  410. p_srcFile->setName(p_destName);
  411. // Add the file to new dir's config
  412. if (p_destDir->addFile(p_srcFile, index)) {
  413. destFile = p_srcFile;
  414. } else {
  415. destFile = NULL;
  416. }
  417. } else {
  418. destFile = p_destDir->addFile(p_destName, -1);
  419. }
  420. if (!destFile) {
  421. return NULL;
  422. }
  423. if (docType != newDocType) {
  424. destFile->convert(docType, newDocType);
  425. }
  426. // We need to copy internal images when it is still markdown.
  427. if (!images.isEmpty()) {
  428. if (newDocType == DocType::Markdown) {
  429. QString parentPath = destFile->retriveBasePath();
  430. int nrPasted = 0;
  431. for (int i = 0; i < images.size(); ++i) {
  432. const ImageLink &link = images[i];
  433. if (!QFileInfo::exists(link.m_path)) {
  434. continue;
  435. }
  436. QString errStr;
  437. bool ret = true;
  438. QString imageFolder = VUtils::directoryNameFromPath(VUtils::basePathFromPath(link.m_path));
  439. QString destImagePath = QDir(parentPath).filePath(imageFolder);
  440. ret = VUtils::makePath(destImagePath);
  441. if (!ret) {
  442. errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
  443. .arg(g_config->c_dataTextStyle).arg(destImagePath);
  444. } else {
  445. destImagePath = QDir(destImagePath).filePath(VUtils::fileNameFromPath(link.m_path));
  446. // Copy or Cut the images accordingly.
  447. if (VUtils::equalPath(destImagePath, link.m_path)) {
  448. ret = false;
  449. } else {
  450. ret = VUtils::copyFile(link.m_path, destImagePath, p_cut);
  451. }
  452. if (ret) {
  453. qDebug() << (p_cut ? "Cut" : "Copy") << "image"
  454. << link.m_path << "->" << destImagePath;
  455. nrPasted++;
  456. } else {
  457. errStr = tr("Please check if there already exists a file <span style=\"%1\">%2</span> "
  458. "and then manually copy it and modify the note accordingly.")
  459. .arg(g_config->c_dataTextStyle).arg(destImagePath);
  460. }
  461. }
  462. if (!ret) {
  463. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  464. tr("Fail to copy image <span style=\"%1\">%2</span> while "
  465. "%5 note <span style=\"%3\">%4</span>.")
  466. .arg(g_config->c_dataTextStyle).arg(link.m_path)
  467. .arg(g_config->c_dataTextStyle).arg(srcPath)
  468. .arg(p_cut ? tr("moving") : tr("copying")),
  469. errStr, QMessageBox::Ok, QMessageBox::Ok, NULL);
  470. }
  471. }
  472. qDebug() << "pasted" << nrPasted << "images";
  473. } else {
  474. // Delete the images.
  475. int deleted = 0;
  476. for (int i = 0; i < images.size(); ++i) {
  477. QFile file(images[i].m_path);
  478. if (file.remove()) {
  479. ++deleted;
  480. }
  481. }
  482. qDebug() << "delete" << deleted << "images since it is not Markdown any more for" << srcPath;
  483. }
  484. }
  485. return destFile;
  486. }
  487. // Copy @p_srcDir to be a sub-directory of @p_destDir with name @p_destName.
  488. VDirectory *VDirectory::copyDirectory(VDirectory *p_destDir, const QString &p_destName,
  489. VDirectory *p_srcDir, bool p_cut)
  490. {
  491. QString srcPath = QDir::cleanPath(p_srcDir->retrivePath());
  492. QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName));
  493. if (VUtils::equalPath(srcPath, destPath)) {
  494. return p_srcDir;
  495. }
  496. VDirectory *srcParentDir = p_srcDir->getParentDirectory();
  497. // Copy the directory
  498. if (!VUtils::copyDirectory(srcPath, destPath, p_cut)) {
  499. return NULL;
  500. }
  501. // Handle VDirectory
  502. int index = -1;
  503. VDirectory *destDir = NULL;
  504. if (p_cut) {
  505. // Remove the directory from config
  506. srcParentDir->removeSubDirectory(p_srcDir);
  507. p_srcDir->setName(p_destName);
  508. // Add the directory to new dir's config
  509. if (p_destDir->addSubDirectory(p_srcDir, index)) {
  510. destDir = p_srcDir;
  511. } else {
  512. destDir = NULL;
  513. }
  514. } else {
  515. destDir = p_destDir->addSubDirectory(p_destName, -1);
  516. }
  517. return destDir;
  518. }
  519. void VDirectory::setExpanded(bool p_expanded)
  520. {
  521. if (p_expanded) {
  522. V_ASSERT(m_opened);
  523. }
  524. m_expanded = p_expanded;
  525. }
  526. void VDirectory::reorderFiles(int p_first, int p_last, int p_destStart)
  527. {
  528. V_ASSERT(m_opened);
  529. V_ASSERT(p_first <= p_last);
  530. V_ASSERT(p_last < m_files.size());
  531. V_ASSERT(p_destStart < p_first || p_destStart > p_last);
  532. V_ASSERT(p_destStart >= 0 && p_destStart <= m_files.size());
  533. auto oriFiles = m_files;
  534. // Reorder m_files.
  535. if (p_destStart > p_last) {
  536. int to = p_destStart - 1;
  537. for (int i = p_first; i <= p_last; ++i) {
  538. // Move p_first to p_destStart every time.
  539. m_files.move(p_first, to);
  540. }
  541. } else {
  542. int to = p_destStart;
  543. for (int i = p_first; i <= p_last; ++i) {
  544. m_files.move(i, to++);
  545. }
  546. }
  547. if (!writeToConfig()) {
  548. qWarning() << "fail to reorder files in config" << p_first << p_last << p_destStart;
  549. m_files = oriFiles;
  550. }
  551. }
  552. VFile *VDirectory::tryLoadFile(QStringList &p_filePath)
  553. {
  554. qDebug() << "directory" << m_name << "tryLoadFile()" << p_filePath.join("/");
  555. if (p_filePath.isEmpty()) {
  556. return NULL;
  557. }
  558. bool opened = isOpened();
  559. if (!open()) {
  560. return NULL;
  561. }
  562. VFile *file = NULL;
  563. #if defined(Q_OS_WIN)
  564. bool caseSensitive = false;
  565. #else
  566. bool caseSensitive = true;
  567. #endif
  568. if (p_filePath.size() == 1) {
  569. // File.
  570. file = findFile(p_filePath.at(0), caseSensitive);
  571. } else {
  572. // Directory.
  573. VDirectory *dir = findSubDirectory(p_filePath.at(0), caseSensitive);
  574. if (dir) {
  575. p_filePath.removeFirst();
  576. file = dir->tryLoadFile(p_filePath);
  577. }
  578. }
  579. if (!file && !opened) {
  580. close();
  581. }
  582. return file;
  583. }