Newer
Older
TriliumScripts / trilium_sync.php
0xRoM on 21 Mar 25 KB initial commit
  1. <?php
  2.  
  3. // Configuration
  4. $apiToken = ""; // API Token (menu->options, ETAPI)
  5. $serverUrl = "http://127.0.0.1:8181"; // location to your trilium instance
  6. $parentNoteId = ""; // Change this to the parent note ID you want to list sub-notes for (The main folder/note)
  7. $localFolder = '/NAS/Docs/trilium-data/'; // folder to sync to
  8.  
  9. // leave below here
  10. $deletedFolders = array();
  11. $deletedFiles = array();
  12.  
  13. function getTriliumVersion($apiToken, $serverUrl) {
  14. $url = "$serverUrl/etapi/app-info";
  15. $ch = curl_init($url);
  16. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  17. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  18. "Authorization: $apiToken",
  19. "Content-Type: application/json"
  20. ]);
  21. $response = curl_exec($ch);
  22. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  23. curl_close($ch);
  24. if ($httpCode !== 200) {
  25. die("Error fetching notes: HTTP $httpCode\n$response");
  26. }
  27. return json_decode($response, true);
  28. }
  29.  
  30. function checkFolderAccess($folderPath) {
  31. if (!is_dir($folderPath) || !is_readable($folderPath)) {
  32. die("Error: Cannot access folder '$folderPath'.");
  33. }
  34. }
  35.  
  36. function fetchSubNotes($parentNoteId, $apiToken, $serverUrl) {
  37. $url = "$serverUrl/etapi/notes/$parentNoteId";
  38. $ch = curl_init($url);
  39. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  40. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  41. "Authorization: $apiToken",
  42. "Content-Type: application/json"
  43. ]);
  44. $response = curl_exec($ch);
  45. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  46. curl_close($ch);
  47. if ($httpCode !== 200) {
  48. die("Error fetching notes: HTTP $httpCode\n$response");
  49. }
  50. return json_decode($response, true);
  51. }
  52.  
  53. function fetchNoteContents($noteId, $apiToken, $serverUrl) {
  54. $url = "$serverUrl/etapi/notes/$noteId/content";
  55. $ch = curl_init($url);
  56. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  57. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  58. "Authorization: $apiToken",
  59. "Content-Type: application/json"
  60. ]);
  61. $response = curl_exec($ch);
  62. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  63. curl_close($ch);
  64. if ($httpCode !== 200) {
  65. return "Error fetching notes: HTTP $httpCode\n$response";
  66. }
  67.  
  68. $response = convertHtmlToText($response);
  69. return $response;
  70. }
  71.  
  72. function updateNoteContents($noteId, $content, $apiToken, $serverUrl) {
  73. $url = "$serverUrl/etapi/notes/$noteId/content";
  74.  
  75. $html = convertTextToHtml($content);
  76.  
  77. // Initialise cURL request
  78. $ch = curl_init($url);
  79. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  80. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); // Set the request method to PUT
  81. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  82. "Authorization: $apiToken",
  83. "Content-Type: text/plain"
  84. ]);
  85. curl_setopt($ch, CURLOPT_POSTFIELDS, $html); // Add formatted HTML to the body
  86. $response = curl_exec($ch);
  87. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  88. curl_close($ch);
  89. if ($httpCode !== 204) {
  90. die("Error updating note: HTTP $httpCode\n$response");
  91. }
  92.  
  93. return;
  94. }
  95.  
  96. function createNote($parentNoteId, $title, $content, $apiToken, $serverUrl) {
  97. $url = "$serverUrl/etapi/create-note";
  98. $data = json_encode([
  99. "parentNoteId" => $parentNoteId,
  100. "title" => $title,
  101. "type" => "text",
  102. "content" => $content
  103. ]);
  104. $ch = curl_init($url);
  105. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  106. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  107. "Authorization: $apiToken",
  108. "Content-Type: application/json"
  109. ]);
  110. curl_setopt($ch, CURLOPT_POST, true);
  111. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  112. $response = curl_exec($ch);
  113. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  114. curl_close($ch);
  115.  
  116. $response = json_decode($response, true);
  117. return $response['note'];
  118. }
  119.  
  120. function deleteNote($noteId, $apiToken, $serverUrl){
  121. $url = "$serverUrl/etapi/notes/$noteId";
  122.  
  123. $ch = curl_init($url);
  124. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  125. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  126. "Authorization: $apiToken",
  127. "Content-Type: application/json"
  128. ]);
  129. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
  130.  
  131. $response = curl_exec($ch);
  132. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  133. curl_close($ch);
  134.  
  135. if ($httpCode === 204) {
  136. return true; // Deletion successful
  137. }
  138. return json_decode($response, true); // Return JSON response for errors
  139. }
  140.  
  141. function fetchAllNotes($noteId, $apiToken, $serverUrl, $localFolder, $path = '') {
  142. $note = fetchSubNotes($noteId, $apiToken, $serverUrl);
  143. if (!empty($note['childNoteIds'])) {
  144. $currentPath = $path . $note['title'] . '/';
  145.  
  146. $fullPath = rtrim($localFolder, '/') . '/' . ltrim($path, '/') . '/' . ltrim($note['title'], '/');
  147. $fullPath = preg_replace('/\/+/', '/', $fullPath);
  148.  
  149. if (is_dir($fullPath)) {
  150. $status = 'Exists';
  151. $modifiedDisk = stat($fullPath)['mtime'];
  152. } else {
  153. $status = 'Missing_Disk';
  154. $modifiedDisk = "";
  155. }
  156. $folder = [
  157. 'noteId' => $noteId,
  158. 'folder' => $note['title'],
  159. 'dateModifiedTrilium' => convertToUnixTimestamp($note['dateModified']),
  160. 'dateModifiedDisk' => $modifiedDisk,
  161. 'parent' => $note['parentNoteIds'][0],
  162. 'children' => [],
  163. 'status' => $status
  164. ];
  165. foreach ($note['childNoteIds'] as $childId) {
  166. $folder['children'][] = fetchAllNotes($childId, $apiToken, $serverUrl, $localFolder, $currentPath);
  167. }
  168. return $folder;
  169. } else {
  170.  
  171. $fullPath = $localFolder . $path . $note['title'];
  172. //echo "does it exist?: $fullPath\n";
  173. $fileExists = file_exists($fullPath) ? 'Exists' : 'Missing_Disk';
  174.  
  175. if ($fileExists === 'Exists' && is_file($fullPath)) {
  176. $modifiedDisk = convertToUnixTimestamp(filemtime($fullPath));
  177. $hashDisk = md5(file_get_contents($fullPath));
  178. } else {
  179. $modifiedDisk = "";
  180. $hashDisk = "";
  181. }
  182.  
  183. $hashTrilium = md5(fetchNoteContents($noteId, $apiToken, $serverUrl));
  184. return [
  185. 'noteId' => $noteId,
  186. 'file' => $note['title'],
  187. 'path' => $fullPath,
  188. 'dateModifiedTrilium' => convertToUnixTimestamp($note['dateModified']),
  189. 'dateModifiedDisk' => $modifiedDisk,
  190. 'parent' => $note['parentNoteIds'][0],
  191. 'hashTrilium' => $hashTrilium,
  192. 'hashDisk' => $hashDisk,
  193. 'hashMach' => "",
  194. 'status' => $fileExists
  195. ];
  196. }
  197. }
  198.  
  199. function scanLocalFiles($directory, $basePath, $path = '') {
  200. $files = [];
  201. $items = scandir($directory);
  202.  
  203. foreach ($items as $item) {
  204. if ($item === '.' || $item === '..') continue;
  205.  
  206. $fullPath = rtrim($directory, '/') . '/' . $item;
  207. $relativePath = str_replace($basePath, '', $fullPath);
  208. $currentPath = rtrim($path, '/') . '/' . $item;
  209.  
  210. if (is_dir($fullPath)) {
  211. $modifiedDisk = stat($fullPath)['mtime'];
  212. $files[] = [
  213. 'noteId' => "",
  214. 'folder' => $item,
  215. 'dateModifiedTrilium' => "",
  216. 'dateModifiedDisk' => $modifiedDisk,
  217. 'children' => scanLocalFiles($fullPath, $basePath, $currentPath), // Recursive call for directories
  218. 'parent' => "",
  219. 'status' => 'Missing_Trilium',
  220. ];
  221. } else {
  222. $hashDisk = md5(file_get_contents($fullPath));
  223. $files[] = [
  224. 'noteId' => "",
  225. 'file' => $item,
  226. 'dateModifiedTrilium' => "",
  227. 'dateModifiedDisk' => convertToUnixTimestamp(filemtime($fullPath)),
  228. 'status' => 'Missing_Trilium',
  229. 'parent' => "",
  230. 'hashTrilium' => '',
  231. 'hashDisk' => $hashDisk,
  232. 'hashMach' => "",
  233. 'path' => ltrim($relativePath, '/'),
  234. ];
  235. }
  236. }
  237.  
  238. return $files;
  239. }
  240.  
  241. function mergeStructures(&$triliumStructure, $localFiles) {
  242. $triliumPaths = [];
  243. if (isset($triliumStructure['children'])) {
  244. foreach ($triliumStructure['children'] as &$child) {
  245. $key = isset($child['folder']) ? $child['folder'] : $child['file'];
  246. $triliumPaths[$key] = &$child;
  247. }
  248. } else {
  249. $triliumStructure['children'] = [];
  250. }
  251. foreach ($localFiles as $localItem) {
  252. $key = isset($localItem['folder']) ? $localItem['folder'] : $localItem['file'];
  253. if (!isset($triliumPaths[$key])) {
  254. if (isset($localItem['path'])) {
  255. $localItem['path'] = preg_replace('|^[^/]+/|', '', $localItem['path']);
  256. }
  257. $triliumStructure['children'][] = $localItem;
  258. } else if (isset($localItem['children'])) {
  259. mergeStructures($triliumPaths[$key], $localItem['children']);
  260. }
  261. }
  262. }
  263.  
  264. function compareFileHashes(&$item) {
  265. // If the item is an array, check if it has children
  266. if (isset($item['children']) && is_array($item['children'])) {
  267. foreach ($item['children'] as &$child) {
  268. compareFileHashes($child);
  269. }
  270. }
  271.  
  272. // Perform hash comparison only if it's a leaf node (has 'file' but no 'children')
  273. if (isset($item['file']) && (!isset($item['children']) || empty($item['children']))) {
  274. if (!empty($item['hashTrilium']) && !empty($item['hashDisk'])) {
  275. if ($item['hashTrilium'] === $item['hashDisk']) {
  276. $item['hashMatch'] = "Match";
  277. } else {
  278. $item['hashMatch'] = 'Diff';
  279. if($item['dateModifiedTrilium'] < $item['dateModifiedDisk']){ $item['status'] ="Update_Trilium"; }
  280. if($item['dateModifiedTrilium'] > $item['dateModifiedDisk']){ $item['status'] ="Update_Disk"; }
  281. }
  282. } else {
  283. $item['hashMatch'] = 'N/A';
  284. }
  285.  
  286. }
  287. }
  288.  
  289. function createTriliumFolder(&$item, $path = '',$parentNoteId = null) {
  290. global $localFolder, $apiToken, $serverUrl;
  291. global $deletedFolders, $deletedFiles;
  292. // Assign the parent's noteId as the parentId for the current item
  293.  
  294. if (isset($item['folder']) && strpos($item['folder'], '[del]') !== 0 ) {
  295.  
  296. $startsWithDeleted = false;
  297. foreach ($deletedFolders as $deletedFolder) {
  298. if (strncmp($path . $item['folder'] ."/", $deletedFolder, strlen($deletedFolder)) === 0) {
  299. $startsWithDeleted = true;
  300. break;
  301. }
  302. }
  303.  
  304. if (!empty($item['status']) && $item['status'] == "Missing_Trilium" ) {
  305. if(!$startsWithDeleted){
  306. $fullpath = $path . $item['folder'] . '/';
  307. echo "[+] creating trilium folder: $fullpath\n";
  308. $newId = createNote($parentNoteId, $item['folder'], "", $apiToken, $serverUrl);
  309. // Update item with new noteId
  310. $item['noteId'] = $newId['noteId'];
  311. $item['dateModifiedTrilium'] = time();
  312. $item['status'] = "Updated";
  313. } else{
  314. $item['status'] = "Skipped";
  315. }
  316. }
  317. }
  318. // Update parentId for immediate children
  319. if (isset($item['children']) && is_array($item['children'])) {
  320. foreach ($item['children'] as &$child) {
  321. if (isset($item['noteId']) && !empty($item['noteId'])) {
  322. $child['parent'] = $item['noteId'];
  323. }
  324. $currentPath = $path . $item['folder'] . '/';
  325. createTriliumFolder($child, $currentPath, $item['noteId']);
  326. }
  327. }
  328. }
  329.  
  330. function createTriliumFile(&$item, $path = '',$parentNoteId = null) {
  331. global $localFolder, $apiToken, $serverUrl;
  332. global $deletedFolders, $deletedFiles;
  333.  
  334. if (isset($item['file']) && strpos($item['file'], '[del]') !== 0) {
  335.  
  336. $startsWithDeleted = false;
  337. foreach ($deletedFolders as $deletedFolder) {
  338. if (strncmp($path , $deletedFolder, strlen($deletedFolder)) === 0) {
  339. $startsWithDeleted = true;
  340. break;
  341. }
  342. }
  343. foreach ($deletedFiles as $deletedFile) {
  344. if (strncmp($path . $item['file'] , $deletedFile, strlen($deletedFile)) === 0) {
  345. $startsWithDeleted = true;
  346. break;
  347. }
  348. }
  349.  
  350. if (!empty($item['status']) && $item['status'] == "Missing_Trilium") {
  351. if(!$startsWithDeleted){
  352. $fullpath = $localFolder . $path . $item['file'] ;
  353. echo "[+] creating trilium note: $fullpath\n";
  354. $contents = file_get_contents($fullpath);
  355. $newId = createNote($parentNoteId, $item['file'], $contents, $apiToken, $serverUrl);
  356. // Update item with new noteId
  357. $item['noteId'] = $newId['noteId'];
  358. $item['dateModifiedTrilium'] = time();
  359. $item['status'] = "Updated";
  360. }else{ $item['status'] = "Skipped"; }
  361. }
  362. }
  363. // Update parentId for immediate children
  364. if (isset($item['children']) && is_array($item['children'])) {
  365. foreach ($item['children'] as &$child) {
  366. if (isset($item['noteId']) && !empty($item['noteId'])) {
  367. $child['parent'] = $item['noteId'];
  368. }
  369. $currentPath = $path . $item['folder'] . '/';
  370. createTriliumFile($child, $currentPath, $item['noteId']);
  371. }
  372. }
  373. }
  374.  
  375. function createDiskFileAndFolder(&$item, $path = '', $parentNoteId = null) {
  376. global $localFolder, $apiToken, $serverUrl;
  377. global $deletedFolders, $deletedFiles;
  378.  
  379. // Assign the parent's noteId as the parentId for the current item
  380. if (isset($item['folder']) && strpos($item['folder'], '[del]') !== 0) {
  381. if (!empty($item['status']) && $item['status'] == "Missing_Disk") {
  382. $fullpath = $localFolder . $path . $item['folder'] . '/';
  383. echo "[+] creating disk folder: $fullpath\n";
  384. mkdir($fullpath, 0777, true);
  385. $item['dateModifiedDisk'] = time();
  386. $item['status'] = "Updated";
  387. }
  388. }
  389.  
  390. if (isset($item['file']) && strpos($item['file'], '[del]') !== 0) {
  391.  
  392. $startsWithDeleted = false;
  393. foreach ($deletedFolders as $deletedFolder) {
  394. if (strncmp($path , $deletedFolder, strlen($deletedFolder)) === 0) {
  395. $startsWithDeleted = true;
  396. break;
  397. }
  398. }
  399. foreach ($deletedFiles as $deletedFile) {
  400. if (strncmp($path . $item['file'] , $deletedFile, strlen($deletedFile)) === 0) {
  401. $startsWithDeleted = true;
  402. break;
  403. }
  404. }
  405.  
  406. if (!empty($item['status']) && $item['status'] == "Missing_Disk") {
  407. if(!$startsWithDeleted){
  408. $fullpath = $localFolder . $path . $item['file'] ;
  409. echo "[+] creating disk note: $fullpath\n";
  410. file_put_contents($fullpath, fetchNoteContents($item['noteId'], $apiToken, $serverUrl) );
  411. $item['dateModifiedDisk'] = time();
  412. $item['status'] = "Updated";
  413. }else{ $item['status'] = "Skipped"; }
  414. }
  415. }
  416. // Update parentId for immediate children
  417. if (isset($item['children']) && is_array($item['children'])) {
  418. foreach ($item['children'] as &$child) {
  419. if (isset($item['noteId']) && !empty($item['noteId'])) {
  420. $child['parent'] = $item['noteId'];
  421. }
  422. $currentPath = $path . $item['folder'] . '/';
  423. createDiskFileAndFolder($child, $currentPath, $item['noteId']);
  424. }
  425. }
  426. }
  427.  
  428. function deleteItem(&$item, $path = '', $parentNoteId = null) {
  429. global $localFolder, $apiToken, $serverUrl;
  430. global $deletedFolders, $deletedFiles;
  431. // Assign the parent's noteId as the parentId for the current item
  432. if (isset($item['folder']) && strpos($item['folder'], '[del]') === 0) {
  433. $item['status'] = "Delete";
  434. $item_fixed = trim(substr($item['folder'], 5)); // Remove "[del]" from the start
  435. $fullpath = $localFolder . $path . $item_fixed . '/';
  436. echo "[-] deleting disk folder: $fullpath\n";
  437. $deletedFolders[] = $path . $item_fixed . '/';
  438. $deletedFolders[] = $path . $item['folder'] . '/';
  439.  
  440. deleteFolderAndContents($fullpath);
  441. deleteNote($item['noteId'], $apiToken, $serverUrl);
  442. }
  443. if (isset($item['file']) && strpos($item['file'], '[del]') === 0) {
  444. $item['status'] = "Delete";
  445.  
  446. $item_fixed = trim(substr($item['file'], 5)); // Remove "[del]" from the start
  447. $fullpath = $localFolder . $path . $item_fixed ;
  448. echo "[-] deleting disk file: $fullpath\n";
  449. $deletedFiles[] = $path . $item_fixed;
  450. $deletedFiles[] = $path . $item['file'];
  451.  
  452. deleteNote($item['noteId'], $apiToken, $serverUrl);
  453. if (file_exists($fullpath)) {
  454. if (is_file($fullpath)) {
  455. unlink($fullpath);
  456. } elseif (is_dir($fullpath)) {
  457. foreach (array_diff(scandir($fullpath), ['.', '..']) as $file) {
  458. unlink("$fullpath") ?: rmdir("$fullpath");
  459. }
  460. rmdir($fullpath);
  461. }
  462. }
  463.  
  464. }
  465. // Update parentId for immediate children
  466. if (isset($item['children']) && is_array($item['children'])) {
  467. foreach ($item['children'] as &$child) {
  468. if (isset($item['folder']) && strpos($item['folder'], '[del]') === 0)
  469. $child['status'] == "Delete";
  470. $currentPath = $path . $item['folder'] . '/';
  471. deleteItem($child, $currentPath, $item['noteId']);
  472. }
  473. }
  474. }
  475.  
  476. function deleteFolderAndContents($folderPath) {
  477. if (!is_dir($folderPath)) {
  478. return false; // Ensure it's a directory
  479. }
  480.  
  481. // Scan the directory for all files and subdirectories
  482. $files = array_diff(scandir($folderPath), array('.', '..'));
  483.  
  484. foreach ($files as $file) {
  485. $filePath = $folderPath . DIRECTORY_SEPARATOR . $file;
  486.  
  487. if (is_dir($filePath)) {
  488. deleteFolderAndContents($filePath);
  489. } else {
  490.  
  491. unlink($filePath);
  492. }
  493. }
  494. return rmdir($folderPath);
  495. }
  496.  
  497. function updateExistingFile(&$item, $path = '', $parentNoteId = null){
  498. global $localFolder, $apiToken, $serverUrl;
  499.  
  500. if (isset($item['file'])) {
  501. if (!empty($item['status']) && $item['status'] == "Update_Trilium") {
  502. $fullpath = $localFolder . $path . $item['file'] ;
  503. echo "[+] updating file on trilium: $fullpath\n";
  504. $content = file_get_contents($fullpath);
  505. updateNoteContents($item['noteId'], $content, $apiToken, $serverUrl);
  506. $item['dateModifiedTrilium'] = time();
  507. $item['status'] = "Updated";
  508. }
  509. }
  510.  
  511. if (isset($item['file'])) {
  512. if (!empty($item['status']) && $item['status'] == "Update_Disk") {
  513. $fullpath = $localFolder . $path . $item['file'] ;
  514. echo "[+] updating file on disk: $fullpath\n";
  515. file_put_contents($fullpath, fetchNoteContents($item['noteId'], $apiToken, $serverUrl) );
  516. $item['dateModifiedDisk'] = time();
  517. $item['status'] = "Updated";
  518. }
  519. }
  520. // Update parentId for immediate children
  521. if (isset($item['children']) && is_array($item['children'])) {
  522. foreach ($item['children'] as &$child) {
  523. $currentPath = $path . $item['folder'] . '/';
  524. updateExistingFile($child, $currentPath, $item['noteId']);
  525. }
  526. }
  527. }
  528.  
  529. function printFolderTable($structure, $path = '', $printHeader = true) {
  530. // Define column widths to ensure consistent formatting
  531. $columnWidths = [
  532. 'path' => 25,
  533. 'folder' => 25,
  534. 'parent' => 13,
  535. 'noteId' => 13,
  536. 'dateModifiedTrilium' => 11,
  537. 'dateModifiedDisk' => 11,
  538. 'status' => 15
  539. ];
  540.  
  541. if ($printHeader) {
  542. echo "- Folders -\n";
  543. echo formatColumn("Folder/Path", 25) .
  544. formatColumn("Folder Name", 25) .
  545. formatColumn("Parent", 13) .
  546. formatColumn("noteId", 13) .
  547. formatColumn("Mod Trl", 11) .
  548. formatColumn("Mod Dsk", 11) .
  549. formatColumn("Status", 15) . "\n";
  550. }
  551.  
  552. if (isset($structure['folder'])) {
  553. $currentPath = $path . $structure['folder'] . '/';
  554. // Print the folder details
  555. echo formatColumn($currentPath, $columnWidths['path']) .
  556. formatColumn($structure['folder'], $columnWidths['folder']) .
  557. formatColumn($structure['parent'], $columnWidths['parent']) .
  558. formatColumn($structure['noteId'], $columnWidths['noteId']) .
  559. formatColumn($structure['dateModifiedTrilium'], $columnWidths['dateModifiedTrilium']) .
  560. formatColumn($structure['dateModifiedDisk'], $columnWidths['dateModifiedDisk']) .
  561. formatColumn($structure['status'], $columnWidths['status']) . "\n";
  562. // Recursively process children
  563. foreach ($structure['children'] as $child) {
  564. printFolderTable($child, $currentPath, false);
  565. }
  566. }
  567. }
  568.  
  569. function printFileTable($structure, $path = '', $printHeader = true) {
  570. // Define column widths to ensure consistent formatting
  571. $columnWidths = [
  572. 'path' => 25,
  573. 'parent' => 13,
  574. 'noteId' => 13,
  575. 'file' => 25,
  576. 'dateModifiedTrilium' => 11,
  577. 'dateModifiedDisk' => 11,
  578. 'hashMatch' => 6,
  579. 'status' => 15
  580. ];
  581.  
  582. if ($printHeader) {
  583. echo "- Files -\n";
  584. echo formatColumn("Folder/Path", 25) .
  585. formatColumn("Filename", 25) .
  586. formatColumn("Parent", 13) .
  587. formatColumn("noteId", 13) .
  588. formatColumn("Mod Trl", 11) .
  589. formatColumn("Mod Dsk", 11) .
  590. formatColumn("Hash", 6) .
  591. formatColumn("Status", 15) . "\n";
  592. }
  593.  
  594. if (isset($structure['folder'])) {
  595. $currentPath = $path . $structure['folder'] . '/';
  596. foreach ($structure['children'] as $child) {
  597. printFileTable($child, $currentPath, false);
  598. }
  599. } else {
  600. // Format and print the table row with aligned columns
  601. echo formatColumn($path, $columnWidths['path']) .
  602. formatColumn($structure['file'], $columnWidths['file']) .
  603. formatColumn($structure['parent'], $columnWidths['parent']) .
  604. formatColumn($structure['noteId'], $columnWidths['noteId']) .
  605. formatColumn($structure['dateModifiedTrilium'], $columnWidths['dateModifiedTrilium']) .
  606. formatColumn($structure['dateModifiedDisk'], $columnWidths['dateModifiedDisk']) .
  607. formatColumn($structure['hashMatch'], $columnWidths['hashMatch']) .
  608. formatColumn($structure['status'], $columnWidths['status']) . "\n";
  609. }
  610. }
  611.  
  612. function convertToUnixTimestamp($timestamp) {
  613. // If timestamp is already an integer (Unix timestamp), return it directly
  614. if (is_int($timestamp)) {
  615. return $timestamp;
  616. }
  617. // For other formats, convert to Unix timestamp
  618. $date = new DateTime($timestamp);
  619. return $date->getTimestamp();
  620. }
  621.  
  622. function convertTextToHtml($text) {
  623. // 1: Plain Text Search
  624. $text = str_replace("&", "&amp;", $text);
  625. $text = str_replace("<", "&lt;", $text);
  626. $text = str_replace(">", "&gt;", $text);
  627.  
  628. // 2: Convert Tabs to 4 &nbsp;'s
  629. $text = str_replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;", $text);
  630.  
  631. // 3: Line Breaks
  632. $text = preg_replace("/\r\n?|\n/", "<br>", $text);
  633.  
  634. // 4: Paragraphs (Replacing two or more line breaks with paragraph tags)
  635. $text = preg_replace("/<br>\s*<br>/", "</p><p>", $text);
  636.  
  637. // 5: Wrap in Paragraph Tags
  638. $text = "<p>" . $text . "</p>";
  639.  
  640. return $text;
  641. }
  642.  
  643. function convertHtmlToText($html) {
  644. // Replace <br>, <br/> and <br /> tags with newlines
  645. $html = str_ireplace(["<br>", "<br/>", "<br />"], "\n", $html);
  646.  
  647. // Replace <p> and </p> tags with double newlines (to separate paragraphs)
  648. $html = str_ireplace("</p>", "\n\n", $html);
  649. $html = str_ireplace("<p>", "", $html); // Remove opening <p> tags
  650.  
  651. // Convert four &nbsp; into a tab (\t)
  652. $html = str_ireplace("&nbsp;&nbsp;&nbsp;&nbsp;", "\t", $html);
  653.  
  654. // Replace other common HTML tags (like <div>, <span>, etc.) by stripping them
  655. $html = strip_tags($html);
  656.  
  657. // Replace HTML entities like &amp;, &lt;, &gt;, &nbsp;, etc. with their corresponding characters
  658. $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
  659.  
  660. // Remove excessive spaces and normalise newlines
  661. $html = preg_replace('/[ ]+/', ' ', $html); // Replace multiple spaces or tabs with a single space
  662. $html = trim($html); // Remove leading/trailing whitespace
  663.  
  664. return $html;
  665. }
  666.  
  667. function formatColumn($str, $width) {
  668. // Function to pad the strings to align the columns
  669. return str_pad($str, $width, ' ', STR_PAD_RIGHT);
  670. }
  671.  
  672. echo "[i] checking connections\n";
  673. $appInfo = getTriliumVersion($apiToken, $serverUrl);
  674. checkFolderAccess($localFolder);
  675. echo "[i] version: ".$appInfo['appVersion']."\n";
  676.  
  677. echo "[i] gathering info\n";
  678. $structure = fetchAllNotes($parentNoteId, $apiToken, $serverUrl, $localFolder);
  679. $localFiles = scanLocalFiles($localFolder."Synced/", $localFolder."Synced/");
  680. mergeStructures($structure, $localFiles);
  681. compareFileHashes($structure);
  682.  
  683. // Print the data tables
  684. printFolderTable($structure);
  685. printFileTable($structure);
  686.  
  687. // syncing operations
  688. deleteItem($structure);
  689. createTriliumFolder($structure);
  690. createTriliumFile($structure);
  691. createDiskFileAndFolder($structure);
  692. updateExistingFile($structure);
  693.  
  694. /*
  695. echo "####### DEBUG ##########\n";
  696. printFolderTable($structure);
  697. printFileTable($structure);
  698. */
Buy Me A Coffee