forked from mirrors/autodoist
Complete overhaul of label-logic.
1) Labels are again used as primary markers for cascading the information. 2) API queue only gets filled with items that require an update. This drastically lowers the size of the queue. 3) Removal of legacy code.master
parent
9e9567efaf
commit
0c47cfa81c
335
autodoist.py
335
autodoist.py
|
@ -1,6 +1,9 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# Autodoist v1.0.3
|
# Autodoist v1.0.3
|
||||||
|
|
||||||
|
global overview_item_ids
|
||||||
|
global overview_item_labels
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
import requests
|
import requests
|
||||||
|
@ -10,7 +13,6 @@ from datetime import datetime
|
||||||
|
|
||||||
from todoist.api import TodoistAPI
|
from todoist.api import TodoistAPI
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
# Version
|
# Version
|
||||||
|
@ -153,6 +155,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
item_type = item['parent_type']
|
item_type = item['parent_type']
|
||||||
item_type_changed = 1
|
item_type_changed = 1
|
||||||
|
item['item_type'] = item_type
|
||||||
except:
|
except:
|
||||||
item_type, item_type_changed = get_type(item, 'item_type')
|
item_type, item_type_changed = get_type(item, 'item_type')
|
||||||
else:
|
else:
|
||||||
|
@ -165,14 +168,30 @@ def main():
|
||||||
labels = item['labels']
|
labels = item['labels']
|
||||||
logging.debug('Updating \'%s\' with label', item['content'])
|
logging.debug('Updating \'%s\' with label', item['content'])
|
||||||
labels.append(label)
|
labels.append(label)
|
||||||
api.items.update(item['id'], labels=labels)
|
|
||||||
|
try:
|
||||||
|
overview_item_ids[str(item['id'])] += 1
|
||||||
|
except:
|
||||||
|
overview_item_ids[str(item['id'])] = 1
|
||||||
|
overview_item_labels[str(item['id'])] = labels
|
||||||
|
|
||||||
def remove_label(item, label):
|
def remove_label(item, label):
|
||||||
if label in item['labels']:
|
if label in item['labels']:
|
||||||
labels = item['labels']
|
labels = item['labels']
|
||||||
logging.debug('Removing \'%s\' of its label', item['content'])
|
logging.debug('Removing \'%s\' of its label', item['content'])
|
||||||
labels.remove(label)
|
labels.remove(label)
|
||||||
api.items.update(item['id'], labels=labels)
|
|
||||||
|
try:
|
||||||
|
overview_item_ids[str(item['id'])] -= 1
|
||||||
|
except:
|
||||||
|
overview_item_ids[str(item['id'])] = -1
|
||||||
|
overview_item_labels[str(item['id'])] = labels
|
||||||
|
|
||||||
|
def update_labels(label_id):
|
||||||
|
filtered_overview_ids = [k for k, v in overview_item_ids.items() if v != 0]
|
||||||
|
for item_id in filtered_overview_ids:
|
||||||
|
labels = overview_item_labels[item_id]
|
||||||
|
api.items.update(item_id, labels = labels)
|
||||||
|
|
||||||
# Check for updates
|
# Check for updates
|
||||||
check_for_update(current_version)
|
check_for_update(current_version)
|
||||||
|
@ -182,85 +201,98 @@ def main():
|
||||||
|
|
||||||
# Main loop
|
# Main loop
|
||||||
while True:
|
while True:
|
||||||
|
overview_item_ids = {}
|
||||||
|
overview_item_labels = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api.sync()
|
api.sync()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
'Error trying to sync with Todoist API: %s' % str(e))
|
'Error trying to sync with Todoist API: %s' % str(e))
|
||||||
else:
|
quit()
|
||||||
for project in api.projects.all():
|
|
||||||
|
|
||||||
# Get project type
|
for project in api.projects.all():
|
||||||
project_type, project_type_changed = get_project_type(project)
|
|
||||||
logging.debug('Project \'%s\' being processed as %s',
|
|
||||||
project['name'], project_type)
|
|
||||||
|
|
||||||
# Get all items for the project
|
# Get project type
|
||||||
items = api.items.all(
|
project_type, project_type_changed = get_project_type(project)
|
||||||
lambda x: x['project_id'] == project['id'])
|
logging.debug('Project \'%s\' being processed as %s',
|
||||||
|
project['name'], project_type)
|
||||||
|
|
||||||
# Change top parents_id in order to sort later on
|
# Get all items for the project
|
||||||
|
items = api.items.all(
|
||||||
|
lambda x: x['project_id'] == project['id'])
|
||||||
|
|
||||||
|
# Change top parents_id in order to sort later on
|
||||||
|
for item in items:
|
||||||
|
if not item['parent_id']:
|
||||||
|
item['parent_id'] = 0
|
||||||
|
|
||||||
|
# Sort by parent_id and filter for completable items
|
||||||
|
items = sorted(items, key=lambda x: (
|
||||||
|
x['parent_id'], x['child_order']))
|
||||||
|
items = list(
|
||||||
|
filter(lambda x: not x['content'].startswith('*'), items))
|
||||||
|
|
||||||
|
# If project type has been changed, clean everything for good measure
|
||||||
|
if project_type_changed == 1:
|
||||||
|
# Remove labels
|
||||||
|
[remove_label(item, label_id) for item in items]
|
||||||
|
# Remove parent types
|
||||||
for item in items:
|
for item in items:
|
||||||
if not item['parent_id']:
|
item['parent_type'] = None
|
||||||
item['parent_id'] = 0
|
|
||||||
|
|
||||||
# Sort by parent_id and filter for completable items
|
# To determine if a sequential task was found
|
||||||
items = sorted(items, key=lambda x: (
|
first_found_project = False
|
||||||
x['parent_id'], x['child_order']))
|
first_found_item = True
|
||||||
items = list(
|
|
||||||
filter(lambda x: not x['content'].startswith('*'), items))
|
|
||||||
|
|
||||||
# If project type has been changed, clean everything for good measure
|
# For all items in this project
|
||||||
if project_type_changed == 1:
|
for item in items:
|
||||||
[remove_label(item, label_id) for item in items]
|
|
||||||
|
|
||||||
# To determine if a task was found on sequential level
|
# Determine which child_items exist, both all and the ones that have not been checked yet
|
||||||
first_found_project = False
|
non_checked_items = list(
|
||||||
first_found_item = True
|
filter(lambda x: x['checked'] == 0, items))
|
||||||
|
child_items_all = list(
|
||||||
|
filter(lambda x: x['parent_id'] == item['id'], items))
|
||||||
|
child_items = list(
|
||||||
|
filter(lambda x: x['parent_id'] == item['id'], non_checked_items))
|
||||||
|
|
||||||
for item in items:
|
# Logic for recurring lists
|
||||||
|
if not args.recurring:
|
||||||
# Determine which child_items exist, both all and the ones that have not been checked yet
|
try:
|
||||||
non_checked_items = list(
|
# If old label is present, reset it
|
||||||
filter(lambda x: x['checked'] == 0, items))
|
if item['r_tag'] == 1:
|
||||||
child_items_all = list(
|
item['r_tag'] = 0
|
||||||
filter(lambda x: x['parent_id'] == item['id'], items))
|
api.items.update(item['id'])
|
||||||
child_items = list(
|
except Exception as e:
|
||||||
filter(lambda x: x['parent_id'] == item['id'], non_checked_items))
|
pass
|
||||||
|
# If option turned on, start recurring logic
|
||||||
# Logic for recurring lists
|
else:
|
||||||
if not args.recurring:
|
if item['parent_id'] == 0:
|
||||||
try:
|
try:
|
||||||
# If old label is present, reset it
|
if item['due']['is_recurring']:
|
||||||
if item['r_tag'] == 1:
|
try:
|
||||||
item['r_tag'] = 0
|
# Check if the T0 task date has changed
|
||||||
except Exception as e:
|
if item['due']['date'] != item['old_date']:
|
||||||
pass
|
# Save the new date
|
||||||
else:
|
|
||||||
if item['parent_id'] == 0:
|
|
||||||
try:
|
|
||||||
if item['due']['is_recurring']:
|
|
||||||
try:
|
|
||||||
# Check if the T0 task date has changed
|
|
||||||
if item['due']['date'] != item['old_date']:
|
|
||||||
# Save the new date
|
|
||||||
item['due']['date'] = item['old_date']
|
|
||||||
|
|
||||||
# Mark children for action
|
|
||||||
for child_item in child_items_all:
|
|
||||||
child_item['r_tag'] = 1
|
|
||||||
except Exception as e:
|
|
||||||
# If date has never been saved before, create a new entry
|
|
||||||
logging.debug(
|
|
||||||
'New recurring task detected: %s' % str(e))
|
|
||||||
item['old_date'] = item['due']['date']
|
item['old_date'] = item['due']['date']
|
||||||
api.items.update(item['id'])
|
api.items.update(item['id'])
|
||||||
|
|
||||||
except Exception as e:
|
# Mark children for action
|
||||||
logging.debug(
|
for child_item in child_items_all:
|
||||||
'Parent not recurring: %s' % str(e))
|
child_item['r_tag'] = 1
|
||||||
pass
|
except Exception as e:
|
||||||
|
# If date has never been saved before, create a new entry
|
||||||
|
logging.debug(
|
||||||
|
'New recurring task detected: %s' % str(e))
|
||||||
|
item['old_date'] = item['due']['date']
|
||||||
|
api.items.update(item['id'])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(
|
||||||
|
'Parent not recurring: %s' % str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
if item['parent_id'] != 0:
|
||||||
try:
|
try:
|
||||||
if item['r_tag'] == 1:
|
if item['r_tag'] == 1:
|
||||||
item.update(checked=0)
|
item.update(checked=0)
|
||||||
|
@ -274,102 +306,97 @@ def main():
|
||||||
logging.debug('Child not recurring: %s' % str(e))
|
logging.debug('Child not recurring: %s' % str(e))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Skip processing an item if it has already been checked
|
# Skip processing an item if it has already been checked
|
||||||
if item['checked'] == 1:
|
if item['checked'] == 1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check item type
|
# Check item type
|
||||||
item_type, item_type_changed = get_item_type(
|
item_type, item_type_changed = get_item_type(
|
||||||
item, project_type)
|
item, project_type)
|
||||||
logging.debug('Identified \'%s\' as %s type',
|
logging.debug('Identified \'%s\' as %s type',
|
||||||
item['content'], item_type)
|
item['content'], item_type)
|
||||||
|
|
||||||
if project_type is None and item_type is None and project_type_changed == 1:
|
# Check the item_type of the project or parent
|
||||||
# Clean the item and its children
|
if item_type is None:
|
||||||
|
if item['parent_id'] == 0:
|
||||||
|
item_type = project_type
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if item['parent_type'] is None:
|
||||||
|
item_type = project_type
|
||||||
|
else:
|
||||||
|
item_type = item['parent_type']
|
||||||
|
except:
|
||||||
|
item_type = project_type
|
||||||
|
else:
|
||||||
|
# Reset in case that parentless task is tagged, overrules project
|
||||||
|
first_found_item = False
|
||||||
|
|
||||||
|
# If it is a parentless task
|
||||||
|
if item['parent_id'] == 0:
|
||||||
|
if project_type == 'sequential':
|
||||||
|
if not first_found_project:
|
||||||
|
add_label(item, label_id)
|
||||||
|
first_found_project = True
|
||||||
|
elif not first_found_item:
|
||||||
|
add_label(item, label_id)
|
||||||
|
first_found_item = True
|
||||||
|
# else:
|
||||||
|
# remove_label(item, label_id)
|
||||||
|
elif project_type == 'parallel':
|
||||||
|
add_label(item, label_id)
|
||||||
|
else:
|
||||||
|
# If no project-type has been defined
|
||||||
|
if item_type:
|
||||||
|
add_label(item, label_id)
|
||||||
|
|
||||||
|
# If there are children
|
||||||
|
if len(child_items) > 0:
|
||||||
|
# Check if item state has changed, if so clean children for good measure
|
||||||
|
if item_type_changed == 1:
|
||||||
|
[remove_label(child_item, label_id)
|
||||||
|
for child_item in child_items]
|
||||||
|
|
||||||
|
# Process sequential tagged items (item_type can overrule project_type)
|
||||||
|
if item_type == 'sequential':
|
||||||
|
for child_item in child_items:
|
||||||
|
# Pass item_type down to the children
|
||||||
|
child_item['parent_type'] = item_type
|
||||||
|
# Pass label down to the first child
|
||||||
|
if child_item['checked'] == 0 and label_id in item['labels']:
|
||||||
|
add_label(child_item, label_id)
|
||||||
|
remove_label(item, label_id)
|
||||||
|
else:
|
||||||
|
# Clean for good measure
|
||||||
|
remove_label(child_item, label_id)
|
||||||
|
# Process parallel tagged items or untagged parents
|
||||||
|
elif item_type == 'parallel':
|
||||||
remove_label(item, label_id)
|
remove_label(item, label_id)
|
||||||
for child_item in child_items:
|
for child_item in child_items:
|
||||||
child_item['parent_type'] = None
|
child_item['parent_type'] = item_type
|
||||||
|
if child_item['checked'] == 0:
|
||||||
|
# child_first_found = True
|
||||||
|
add_label(child_item, label_id)
|
||||||
|
|
||||||
# We can immediately continue
|
# If item is too far in the future, remove the next_action tag and skip
|
||||||
continue
|
if args.hide_future > 0 and 'due_date_utc' in item.data and item['due_date_utc'] is not None:
|
||||||
else:
|
due_date = datetime.strptime(
|
||||||
# Define the item_type
|
item['due_date_utc'], '%a %d %b %Y %H:%M:%S +0000')
|
||||||
if item_type is None:
|
future_diff = (
|
||||||
item_type = project_type
|
due_date - datetime.utcnow()).total_seconds()
|
||||||
else:
|
if future_diff >= (args.hide_future * 86400):
|
||||||
# Reset in case that task is tagged as sequential
|
remove_label(item, label_id)
|
||||||
first_found_item = False
|
continue
|
||||||
|
|
||||||
# If it is a parentless task
|
# Commit the queue with changes
|
||||||
if len(child_items) == 0:
|
update_labels(label_id)
|
||||||
if item['parent_id'] == 0:
|
|
||||||
if project_type == 'sequential':
|
|
||||||
if not first_found_project:
|
|
||||||
add_label(item, label_id)
|
|
||||||
first_found_project = True
|
|
||||||
elif not first_found_item:
|
|
||||||
add_label(item, label_id)
|
|
||||||
first_found_item = True
|
|
||||||
else:
|
|
||||||
remove_label(item, label_id)
|
|
||||||
elif project_type == 'parallel':
|
|
||||||
add_label(item, label_id)
|
|
||||||
else:
|
|
||||||
# If only the item type has been defined
|
|
||||||
if item_type:
|
|
||||||
add_label(item, label_id)
|
|
||||||
|
|
||||||
# If there are children, label them instead
|
if len(api.queue):
|
||||||
if len(child_items) > 0:
|
logging.debug(
|
||||||
child_first_found = False
|
'%d changes queued for sync... commiting to Todoist.', len(api.queue))
|
||||||
|
api.commit()
|
||||||
# Check if state has changed, if so clean for good measure
|
else:
|
||||||
if item_type_changed == 1:
|
logging.debug('No changes queued, skipping sync.')
|
||||||
[remove_label(child_item, label_id)
|
|
||||||
for child_item in child_items]
|
|
||||||
|
|
||||||
# Process sequential tagged items (item_type can overrule project_type)
|
|
||||||
if item_type == 'sequential':
|
|
||||||
for child_item in child_items:
|
|
||||||
if child_item['checked'] == 0 and not child_first_found and not first_found_project:
|
|
||||||
first_found_project = True
|
|
||||||
child_first_found = True
|
|
||||||
add_label(child_item, label_id)
|
|
||||||
child_item['parent_type'] = item_type
|
|
||||||
elif child_item['checked'] == 0 and not child_first_found and not first_found_item:
|
|
||||||
first_found_item = True
|
|
||||||
child_first_found = True
|
|
||||||
add_label(child_item, label_id)
|
|
||||||
child_item['parent_type'] = item_type
|
|
||||||
else:
|
|
||||||
remove_label(child_item, label_id)
|
|
||||||
# Process parallel tagged items or untagged parents
|
|
||||||
elif item_type == 'parallel':
|
|
||||||
for child_item in child_items:
|
|
||||||
if child_item['checked'] == 0:
|
|
||||||
child_first_found = True
|
|
||||||
add_label(child_item, label_id)
|
|
||||||
child_item['parent_type'] = item_type
|
|
||||||
|
|
||||||
# Remove the label from the parent (needed for if recurring list is reset)
|
|
||||||
if item_type and child_first_found:
|
|
||||||
remove_label(item, label_id)
|
|
||||||
|
|
||||||
# If item is too far in the future, remove the next_action tag and skip
|
|
||||||
if args.hide_future > 0 and 'due_date_utc' in item.data and item['due_date_utc'] is not None:
|
|
||||||
due_date = datetime.strptime(
|
|
||||||
item['due_date_utc'], '%a %d %b %Y %H:%M:%S +0000')
|
|
||||||
future_diff = (
|
|
||||||
due_date - datetime.utcnow()).total_seconds()
|
|
||||||
if future_diff >= (args.hide_future * 86400):
|
|
||||||
remove_label(item, label_id)
|
|
||||||
continue
|
|
||||||
if len(api.queue):
|
|
||||||
logging.debug(
|
|
||||||
'%d changes queued for sync... commiting to Todoist.', len(api.queue))
|
|
||||||
api.commit()
|
|
||||||
else:
|
|
||||||
logging.debug('No changes queued, skipping sync.')
|
|
||||||
|
|
||||||
# If onetime is set, exit after first execution.
|
# If onetime is set, exit after first execution.
|
||||||
if args.onetime:
|
if args.onetime:
|
||||||
|
|
Loading…
Reference in New Issue