Skip to content

Commit

Permalink
Safer file operations (#88)
Browse files Browse the repository at this point in the history
* fix: issue with non en-US date/time representation causing parsing errors
fix: edge case where tasks were not getting closed properly on chekcbox click
closes #64

* fix: issue with non en-US date/time representation causing parsing errors
fix: edge case where tasks were not getting closed properly on checkbox click
update changelog
closes #64

* Fixes due to TickTick API changes
Fix API headers in requests per TickTick protocol change.
Improve first login experience to prevent default folder/project issues

* Fixes due to TickTick API changes
Change task fetch checkpoint logic (Quickfix)
Ensure token and inbox ID always correct
defensive code around first sync logic

* Fixes add defensive code
handle fileMetaData doesn't exit for a file
handle invalid file being added to fileMetataData
improved task sorting while getting from TT to avoid loss of parent/child relationships.

* Chore: wrap file operations in try catches to prevent corruption
  • Loading branch information
thesamim authored Mar 22, 2024
1 parent bb1d506 commit 7a9216c
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 58 deletions.
2 changes: 1 addition & 1 deletion dist/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "tickticksync",
"name": "TickTickSync",
"version": "1.0.21",
"version": "1.0.22",
"minAppVersion": "1.0.0",
"description": "Sync TickTick tasks to Obsidian, and Obsidian tasks to TickTick",
"author": "thesamim",
Expand Down
73 changes: 43 additions & 30 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,8 +650,10 @@ export default class TickTickSync extends Plugin {
if (!(this.checkModuleClass())) {
return;
}

console.log('TickTick scheduled synchronization task started at', new Date().toLocaleString());
try {

if (!await this.checkAndHandleSyncLock()) {
console.error('TickTick scheduled synchronization task terminated for sync loc at', new Date().toLocaleString());
return;
Expand Down Expand Up @@ -693,40 +695,51 @@ export default class TickTickSync extends Plugin {

//Check for duplicates before we do anything

const result = this.cacheOperation?.checkForDuplicates(newFilesToSync);
if (result?.duplicates && (JSON.stringify(result.duplicates) != "{}") ) {
let dupText = '';
for (let duplicatesKey in result.duplicates) {
dupText += "Task: " + duplicatesKey + '\nin files: \n';
result.duplicates[duplicatesKey].forEach( file => {dupText += file + "\n"})
try {
const result = this.cacheOperation?.checkForDuplicates(newFilesToSync);

if (result?.duplicates && (JSON.stringify(result.duplicates) != "{}")) {
let dupText = '';
for (let duplicatesKey in result.duplicates) {
dupText += "Task: " + duplicatesKey + '\nin files: \n';
result.duplicates[duplicatesKey].forEach(file => {
dupText += file + "\n"
})
}
const msg =
"Found duplicates in MetaData.\n\n" +
`${dupText}` +
"\nPlease fix manually. This causes unpredictable results" +
"\nPlease open an issue in the TickTickSync repository if you continue to see this issue." +
"\n\nTo prevent data corruption. Sync is aborted."
console.log("Metadata Duplicates: ", result.duplicates);
new Notice(msg, 0);
return;
}
const msg =
"Found duplicates in MetaData.\n\n" +
`${dupText}` +
"\nPlease fix manually. This causes unpredictable results" +
"\nPlease open an issue in the TickTickSync repository if you continue to see this issue." +
"\n\nTo prevent data corruption. Sync is aborted."
console.log("Metadata Duplicates: ", result.duplicates);
new Notice(msg, 0);
return;
}


const duplicateTasksInFiles = await this.fileOperation?.checkForDuplicates(filesToSync, result?.taskIds)
if (duplicateTasksInFiles && (JSON.stringify(duplicateTasksInFiles) != "{}")) {
let dupText = ""
for (let duplicateTasksInFilesKey in duplicateTasksInFiles) {
dupText += "Task: " + duplicateTasksInFilesKey + "\nFound in Files: \n"
duplicateTasksInFiles[duplicateTasksInFilesKey].forEach(file => {dupText += file + "\n"})
const duplicateTasksInFiles = await this.fileOperation?.checkForDuplicates(filesToSync, result?.taskIds)
if (duplicateTasksInFiles && (JSON.stringify(duplicateTasksInFiles) != "{}")) {
let dupText = ""
for (let duplicateTasksInFilesKey in duplicateTasksInFiles) {
dupText += "Task: " + duplicateTasksInFilesKey + "\nFound in Files: \n"
duplicateTasksInFiles[duplicateTasksInFilesKey].forEach(file => {
dupText += file + "\n"
})
}
const msg =
"Found duplicates in Files.\n\n" +
`${dupText}` +
"\nPlease fix manually. This causes unpredictable results" +
"\nPlease open an issue in the TickTickSync repository if you continue to see this issue." +
"\n\nTo prevent data corruption. Sync is aborted."
new Notice(msg, 0)
return;
}
const msg =
"Found duplicates in Files.\n\n" +
`${dupText}` +
"\nPlease fix manually. This causes unpredictable results" +
"\nPlease open an issue in the TickTickSync repository if you continue to see this issue." +
"\n\nTo prevent data corruption. Sync is aborted."
new Notice(msg, 0)
return;
} catch (Error) {
console.error(Error)
new Notice(`Duplicate check failed: ${Error}`, 0)
return
}


Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "tickticksync",
"name": "TickTickSync",
"version": "1.0.21",
"version": "1.0.22",
"minAppVersion": "1.0.0",
"description": "Sync TickTick tasks to Obsidian, and Obsidian tasks to TickTick",
"author": "thesamim",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tickticksync",
"version": "1.0.21",
"version": "1.0.22",
"description": "Sync TickTick tasks to Obsidian, and Obsidian tasks to TickTick",
"main": "main.js",
"scripts": {
Expand Down
67 changes: 44 additions & 23 deletions src/fileOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,25 +486,34 @@ export class FileOperation {
return;
}

for (const file in fileMetadata) {
const currentFile = this.app.vault.getAbstractFileByPath(file)
const content = await this.app.vault.read(currentFile)
for (let taskListKey in taskList) {
if (content.includes(taskListKey)) {
if (taskIds[taskListKey]) {
if (!duplicates[taskListKey]) {
duplicates[taskListKey] = [taskIds[taskListKey]];
let fileName;
try {

for (const file in fileMetadata) {
fileName = file;
const currentFile = this.app.vault.getAbstractFileByPath(file)
const content = await this.app.vault.read(currentFile)
for (let taskListKey in taskList) {
if (content.includes(taskListKey)) {
if (taskIds[taskListKey]) {
if (!duplicates[taskListKey]) {
duplicates[taskListKey] = [taskIds[taskListKey]];
}
duplicates[taskListKey].push(file);
} else {
taskIds[taskListKey] = file;
}
duplicates[taskListKey].push(file);
} else {
taskIds[taskListKey] = file;

}

}

}
return duplicates;
} catch (Fail) {
const errMsg = `File [${fileName}] not found, or is locked. If file exists, Please try again later.`
console.error(Fail, errMsg)
throw new Error(errMsg)
}
return duplicates;
}

//Yes, I know this belongs in taskParser, but I don't feel like messing with it right now.
Expand Down Expand Up @@ -692,7 +701,14 @@ export class FileOperation {
if (child && child.items) {
numChildTaskItems = child.items.length;
}
await this.deleteTaskFromSpecificFile(filepath, child.id, child.title, numChildTaskItems, false);
try {
await this.deleteTaskFromSpecificFile(filepath, child.id, child.title, numChildTaskItems, false);
} catch (error) {
//assume parent child moved, child didn't. Further assume it will be taken care of on the next sync.
console.log("Child ", childId," not found for parent: ", newTask.id)
continue;
}

}
}

Expand All @@ -715,15 +731,20 @@ export class FileOperation {
}
//get it from cache
const currentChild = await this.plugin.cacheOperation?.loadTaskFromCacheID(childId);
currentChild.parentId = newTask.id;
currentChild.projectId = newTask.projectId;
const numChildTaskItems = currentChild.items?.length

await this.moveTask(filepath, currentChild, numChildTaskItems, currentChild.id, currentChild.projectId);
const currentChildHasChildren = this.hasChildren(currentChild);
if (currentChildHasChildren) {
const currentChild = await this.plugin.cacheOperation?.loadTaskFromCacheID(childId);
await this.moveChildTasks(currentChild, toBeProcessed, filepath);
if (currentChild) {
currentChild.parentId = newTask.id;
currentChild.projectId = newTask.projectId;
const numChildTaskItems = currentChild.items?.length

await this.moveTask(filepath, currentChild, numChildTaskItems, currentChild.id, currentChild.projectId);
const currentChildHasChildren = this.hasChildren(currentChild);
if (currentChildHasChildren) {
const currentChild = await this.plugin.cacheOperation?.loadTaskFromCacheID(childId);
await this.moveChildTasks(currentChild, toBeProcessed, filepath);
}
} else {
//weird move. Don't break. Assume it will be taken care of on the next sync
console.log("Child not found: ", childId)
}

}
Expand Down
2 changes: 1 addition & 1 deletion src/syncModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class SyncMan {
const deletedTaskIDs = fileMetadata_TickTickTasks.map((taskDetail) => taskDetail.taskId);
if (deletedTaskIDs.length > 0) {
//TODO: Assuming that if they for real deleted everything, it will get caught on the next sync
console.error("All tasks will be deleted.", file, currentFileValue, filepath);
console.error("Content not readable.", currentFileValue, filepath, " file is possibly open elsewhere?");

// new Notice(`All content from ${file} APPEARS to have been removed.\n` +
// "If this is correct, please confirm task deletion.", 0)
Expand Down
3 changes: 2 additions & 1 deletion versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
"1.0.18": "1.0.0",
"1.0.19": "1.0.0",
"1.0.20": "1.0.0",
"1.0.21": "1.0.0"
"1.0.21": "1.0.0",
"1.0.22": "1.0.0"
}

0 comments on commit 7a9216c

Please sign in to comment.