-
-
Notifications
You must be signed in to change notification settings - Fork 237
feat: Allow persistent GCal deletions #482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: Allow persistent GCal deletions #482
Conversation
|
Thank you very much for contributing. Upon first inspection, the code does not distinguish between So a freshly set up script would never add any events cause the script treats all events as having been manually deleted. Am i missing something? |
|
@jonas0b1011001 Oof, yeah, refreshed this morning and ran into that. Came here this mornign to convert back to draft and saw your comment. So thanks for the speedy and thorough review! Not sure why my original manual tests didn't catch this. I'll come back with a revision. |
d1baee8 to
b76d255
Compare
|
@jonas0b1011001 Totally revised the approach. Now status (from untracked, tracked, to deleted) is managed via a simple state machine. States are stored in memory. I also updated my test process (see PR desc.) to fully wipe the test calendar and deleted multiple different types of events. Let me know if you find any other issues or if there's something you think should be added. |
|
Great, will take a look on the weekend! |
I've sometimes wished I had this feature, but from a user standpoint is there any way to get an event back after deletion? Or what if the source event changes, would it then repopulate? I'm not a good enough programmer to review your code so maybe you've already accounted for this!?! Just some thoughts from a user. Thanks! |
|
@Lonestarjeepin they can add a duplicate event in the event source and it'll add back in since the originating ID will be different. Other than that, I'm open to ideas. If the source event is just updated, I don't believe it will not repopulate since the ID doesn't change. Perhaps there's an edge case for an event being deleted (for a conflict), but then moved... on the downstream GCal I think it would still be deleted. Not sure how to handle that. |
Good questions, just some quick thoughts on it (still have not had a closer look at the new code):
Google Calendar has a trash bin were deleted events sit for 30 days. The user can restore events from there.
First we would need a way to track that it changed. I guess that would only be possible if we not only track the state of an event but also it's
|
|
I did some testing, for me the current version fails the test procedure you described in your first comment. Normal events are getting readded on every run. I'll need more time for a full review/test. |
|
Hmmmm... is there a public ics calendar you are using for tests? Maybe it's specific to the source calendar I'm using. I've got calendars from my work outlook, but it doesn't give me much flexibility on test cases. Perhaps there's an ics file somewhere with a broad enough set of test events that we can both work from? I'll step through the code again to see if there's a logic gap I missed. Also any consideration for adding a test suite for the repo? |
I was using this simple ics.
Do you see my comments on the code or are they invisible unless i finish my review?
Nothing planned atm. |
|
I don't think I can see the comments until you close the review. Obviously close without approval and I'll debug some more and push some updates. Thanks for working with me on this1 |
jonas0b1011001
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will need to take a closer look at the recurrence part of it as i'm 100% sure it will cause issues at some point (as it does with every feature of the script)
| case EVENT_STATE.TRACKED: | ||
| // Check if the event is still in the source | ||
| if (!icsEventsIds.includes(fullEventId)) { | ||
| setEventState(fullEventId, EVENT_STATE.REMOVED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is unreachable, we are iterating over all source events.
Removed would have to be set in the cleanup function where we check for events in the target calendar that are not in the source file.
|
|
||
| case EVENT_STATE.REMOVED: | ||
| // If the event reappears in the source, re-add it | ||
| if (icsEventsIds.includes(fullEventId)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unneccessary if statement, at this point we know it reappeared.
| break; | ||
|
|
||
| case EVENT_STATE.MANUALLY_DELETED: | ||
| // Do nothing, as the event should not be re-added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//Allow recovering manually deleted events
if (calendarEventsIds.includes(fullEventId)) {
setEventState(fullEventId, EVENT_STATE.TRACKED);
if (isRecurringInstance) {
processEventInstance(event);
} else {
processEvent(event, calendarTz);
}
}
| break; | ||
|
|
||
| case EVENT_STATE.MANUALLY_DELETED: | ||
| // Do nothing, as the event should not be re-added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would also be the place to check if the manually deleted event was updated.
We would have to
- save to latest hash in processEvent/processEventInstance to know which hash the event had when deleted
- calculate the current hash of the event in the source
event.removeProperty('dtstamp');
let digest = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, event.toString(), Utilities.Charset.UTF_8).toString();
- compare saved and current hash
- decide what to do in case the hash changed. readd yes/no, notify the user yes/no
| setEventState(fullEventId, EVENT_STATE.TRACKED); | ||
| break; | ||
|
|
||
| case EVENT_STATE.TRACKED: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do non-recurring events transition from TRACKED to MANUALLY_DELETED?
You are missing
if (!calendarEventsIds.includes(fullEventId)) {
setEventState(fullEventId, EVENT_STATE.MANUALLY_DELETED);
}
| * @param {string} fullEventId - The full event ID to search for, in the format "eventId_recurrenceId" | ||
| * @return {Array.<Calendar.Event>} An array with a single element if a matching event instance is found, or an empty array if no match is found. | ||
| */ | ||
| function findEventInstanceToPatch(recEvent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This neither matches the doc nor the function call in Line 876.
| Logger.log("Processing " + recurringEvents.length + " Recurrence Instances!"); | ||
| for (var recEvent of recurringEvents){ | ||
| processEventInstance(recEvent); | ||
| processEventWithState(recEvent, calendarTz, true); // Use the state machine for recurring instances |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a need for instances to go through the state machine(s) 3 times?
|
|
||
| // Check the current state of the event instance | ||
| switch (currentState) { | ||
| case EVENT_STATE.UNTRACKED: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unreachable - all instances will be set to TRACKED the first time they enter the statemachine in processEventWithState().
| * @param {string} fullEventId - The full event ID to search for, in the format "eventId_recurrenceId" | ||
| * @return {Array.<Calendar.Event>} An array with a single element if a matching event instance is found, or an empty array if no match is found. | ||
| */ | ||
| function findEventInstanceToPatch(recEvent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is incomplete and will only ever match instances that where patched by the script (privateExtendedProperty : "rec-id="), however we need this to be able to match 'all' instances, i.e. instances that are created by google from a recurring event (recurrence rules are 'expanded' by google and not by the script)
Need to test:
function findEventInstanceToPatch(recEvent) {
if (recEvent.recurringEventId.length > 10 && recEvent.recurringEventId.substr(-1) !== "Z"){
recEvent.recurringEventId += "Z";
}
eventInstanceToPatch = callWithBackoff(function(){
return Calendar.Events.list(targetCalendarId,
{ singleEvents : true,
orderBy : "startTime",
timeZone: "etc/GMT",
privateExtendedProperty : "fromGAS=true",
privateExtendedProperty : "id=" + recEvent.extendedProperties.private["id"]
}).items;
}, defaultMaxRetries);
// If the event instance is not found, it may be deleted.
return eventInstanceToPatch.filter(function(e){
return ((e.originalStartTime.dateTime || e.originalStartTime.date) == recEvent.recurringEventId);
});
}
Type: feat
What kind of change does this PR introduce?
What is the current behavior?
What is the new behavior?
Does this PR introduce a breaking change?
Testing Process