mirror of https://github.com/Hoffelhas/autodoist
Core labelling functionality now seems to function. Still need to fix one bug with cleaning children if task_type is set.
parent
4a236153d0
commit
0c64e99455
250
autodoist.py
250
autodoist.py
|
@ -45,10 +45,15 @@ def close_connection(connection):
|
||||||
# Execute any SQLite query passed to it in the form of string
|
# Execute any SQLite query passed to it in the form of string
|
||||||
|
|
||||||
|
|
||||||
def execute_query(connection, query):
|
def execute_query(connection, query, *args):
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
try:
|
try:
|
||||||
|
value = args[0]
|
||||||
|
cursor.execute(query,(value,)) # Useful to pass None/NULL value correctly
|
||||||
|
except:
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
|
|
||||||
|
try:
|
||||||
connection.commit()
|
connection.commit()
|
||||||
logging.debug("Query executed: {}".format(query))
|
logging.debug("Query executed: {}".format(query))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -111,22 +116,15 @@ def db_update_value(connection, model, column, value):
|
||||||
db_name = 'projects'
|
db_name = 'projects'
|
||||||
goal = 'project_id'
|
goal = 'project_id'
|
||||||
|
|
||||||
query = """
|
query = """UPDATE %s SET %s = ? WHERE %s = %r""" % (db_name, column, goal, model.id)
|
||||||
UPDATE
|
|
||||||
%s
|
|
||||||
SET
|
|
||||||
%s = %r
|
|
||||||
WHERE
|
|
||||||
%s = %r
|
|
||||||
""" % (db_name, column, value, goal, model.id)
|
|
||||||
|
|
||||||
result = execute_query(connection, query)
|
result = execute_query(connection, query, value)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.debug(f"The error '{e}' occurred")
|
logging.debug(f"The error '{e}' occurred")
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# Check if the id of a model exists, if not, add to database
|
# Check if the id of a model exists, if not, add to database
|
||||||
|
|
||||||
|
@ -159,10 +157,10 @@ def db_check_existance(connection, model):
|
||||||
if isinstance(model, Section):
|
if isinstance(model, Section):
|
||||||
q_create = """
|
q_create = """
|
||||||
INSERT INTO
|
INSERT INTO
|
||||||
sections (section_id, project_type, section_type)
|
sections (section_id, section_type)
|
||||||
VALUES
|
VALUES
|
||||||
(%r, %s, %s);
|
(%r, %s);
|
||||||
""" % (model.id, 'NULL', 'NULL')
|
""" % (model.id, 'NULL')
|
||||||
|
|
||||||
if isinstance(model, Project):
|
if isinstance(model, Project):
|
||||||
q_create = """
|
q_create = """
|
||||||
|
@ -198,8 +196,7 @@ def initialise_sqlite():
|
||||||
q_create_sections_table = """
|
q_create_sections_table = """
|
||||||
CREATE TABLE IF NOT EXISTS sections (
|
CREATE TABLE IF NOT EXISTS sections (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
sections_id INTEGER,
|
section_id INTEGER,
|
||||||
project_type TEXT,
|
|
||||||
section_type
|
section_type
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
@ -571,8 +568,6 @@ def add_label(connection, task, dominant_type, label, overview_task_ids, overvie
|
||||||
overview_task_ids[task.id] = 1
|
overview_task_ids[task.id] = 1
|
||||||
overview_task_labels[task.id] = labels
|
overview_task_labels[task.id] = labels
|
||||||
|
|
||||||
db_update_value(connection, task, 'task_type', dominant_type)
|
|
||||||
|
|
||||||
# Logic to track removal of a label from a task
|
# Logic to track removal of a label from a task
|
||||||
|
|
||||||
|
|
||||||
|
@ -851,6 +846,14 @@ def autodoist_magic(args, api, connection):
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
print(error)
|
print(error)
|
||||||
|
|
||||||
|
# If a project type has changed, clean all tasks in this project for good measure
|
||||||
|
if next_action_label is not None:
|
||||||
|
if project_type_changed == 1:
|
||||||
|
for task in project_tasks:
|
||||||
|
remove_label(task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
|
db_update_value(connection, task, 'task_type', None)
|
||||||
|
db_update_value(connection, task, 'parent_type', None)
|
||||||
|
|
||||||
# Run for both non-sectioned and sectioned tasks
|
# Run for both non-sectioned and sectioned tasks
|
||||||
|
|
||||||
# Get completed tasks:
|
# Get completed tasks:
|
||||||
|
@ -890,46 +893,39 @@ def autodoist_magic(args, api, connection):
|
||||||
args, connection, section)
|
args, connection, section)
|
||||||
|
|
||||||
# Get all tasks for the section
|
# Get all tasks for the section
|
||||||
tasks = [x for x in project_tasks if x.section_id
|
section_tasks = [x for x in project_tasks if x.section_id
|
||||||
== section.id]
|
== section.id]
|
||||||
|
|
||||||
# Change top tasks parents_id from 'None' to '0' in order to numerically sort later on
|
# Change top tasks parents_id from 'None' to '0' in order to numerically sort later on
|
||||||
for task in tasks:
|
for task in section_tasks:
|
||||||
if not task.parent_id:
|
if not task.parent_id:
|
||||||
task.parent_id = 0
|
task.parent_id = 0
|
||||||
|
|
||||||
# Sort by parent_id and child order
|
# Sort by parent_id and child order
|
||||||
# In the past, Todoist used to screw up the tasks orders, so originally I processed parentless tasks first such that children could properly inherit porperties.
|
# In the past, Todoist used to screw up the tasks orders, so originally I processed parentless tasks first such that children could properly inherit porperties.
|
||||||
# With the new API this seems to be in order, but I'm keeping this just in case for now. TODO: Could be used for optimization in the future.
|
# With the new API this seems to be in order, but I'm keeping this just in case for now. TODO: Could be used for optimization in the future.
|
||||||
tasks = sorted(tasks, key=lambda x: (
|
section_tasks = sorted(section_tasks, key=lambda x: (
|
||||||
int(x.parent_id), x.order))
|
int(x.parent_id), x.order))
|
||||||
|
|
||||||
# If a type has changed, clean all tasks in this section for good measure
|
# If a type has changed, clean all tasks in this section for good measure
|
||||||
if next_action_label is not None:
|
if next_action_label is not None:
|
||||||
if project_type_changed == 1 or section_type_changed == 1:
|
if section_type_changed == 1:
|
||||||
# Remove labels
|
for task in section_tasks:
|
||||||
[remove_label(task, next_action_label, overview_task_ids,
|
remove_label(task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
overview_task_labels) for task in tasks]
|
db_update_value(connection, task, 'task_type', None)
|
||||||
# Remove parent types
|
db_update_value(connection, task, 'parent_type', None)
|
||||||
# for task in tasks:
|
|
||||||
# task.parent_type = None #TODO: METADATA
|
|
||||||
|
|
||||||
# For all tasks in this section
|
# For all tasks in this section
|
||||||
for task in tasks:
|
for task in section_tasks:
|
||||||
dominant_type = None # Reset
|
dominant_type = None # Reset
|
||||||
|
|
||||||
db_check_existance(connection, task)
|
db_check_existance(connection, task)
|
||||||
|
|
||||||
# Possible nottes routine for the future
|
|
||||||
# notes = api.notes.all() TODO: Quick notes test to see what the impact is?
|
|
||||||
# note_content = [x['content'] for x in notes if x['item_id'] == item['id']]
|
|
||||||
# print(note_content)
|
|
||||||
|
|
||||||
# Determine which child_tasks exist, both all and the ones that have not been checked yet
|
# Determine which child_tasks exist, both all and the ones that have not been checked yet
|
||||||
non_completed_tasks = list(
|
non_completed_tasks = list(
|
||||||
filter(lambda x: not x.is_completed, tasks))
|
filter(lambda x: not x.is_completed, section_tasks))
|
||||||
child_tasks_all = list(
|
child_tasks_all = list(
|
||||||
filter(lambda x: x.parent_id == task.id, tasks))
|
filter(lambda x: x.parent_id == task.id, section_tasks))
|
||||||
child_tasks = list(
|
child_tasks = list(
|
||||||
filter(lambda x: x.parent_id == task.id, non_completed_tasks))
|
filter(lambda x: x.parent_id == task.id, non_completed_tasks))
|
||||||
|
|
||||||
|
@ -971,13 +967,21 @@ def autodoist_magic(args, api, connection):
|
||||||
task_type, task_type_changed = get_task_type(
|
task_type, task_type_changed = get_task_type(
|
||||||
args, connection, task)
|
args, connection, task)
|
||||||
|
|
||||||
|
# If task type has changed, clean all of its children for good measure
|
||||||
|
if next_action_label is not None:
|
||||||
|
if task_type_changed == 1:
|
||||||
|
for child_task in child_tasks:
|
||||||
|
remove_label(child_task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
|
db_update_value(connection, child_task, 'task_type', None)
|
||||||
|
db_update_value(connection, child_task, 'parent_type', None)
|
||||||
|
|
||||||
# Determine hierarchy types for logic
|
# Determine hierarchy types for logic
|
||||||
hierarchy_types = [task_type,
|
hierarchy_types = [task_type,
|
||||||
section_type, project_type]
|
section_type, project_type]
|
||||||
hierarchy_boolean = [type(x) != type(None)
|
hierarchy_boolean = [type(x) != type(None)
|
||||||
for x in hierarchy_types]
|
for x in hierarchy_types]
|
||||||
|
|
||||||
# If it is a parentless task
|
# If it is a parentless task, set task type based on hierarchy
|
||||||
if task.parent_id == 0:
|
if task.parent_id == 0:
|
||||||
if hierarchy_boolean[0]:
|
if hierarchy_boolean[0]:
|
||||||
# Inherit task type
|
# Inherit task type
|
||||||
|
@ -1011,6 +1015,9 @@ def autodoist_magic(args, api, connection):
|
||||||
elif project_type == 'parallel' or project_type == 'p-s':
|
elif project_type == 'parallel' or project_type == 'p-s':
|
||||||
add_label(
|
add_label(
|
||||||
connection, task, dominant_type, next_action_label, overview_task_ids, overview_task_labels)
|
connection, task, dominant_type, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
|
else:
|
||||||
|
# Parentless task has no type, so skip any children.
|
||||||
|
continue
|
||||||
|
|
||||||
# Mark other conditions too
|
# Mark other conditions too
|
||||||
if first_found_section == False and hierarchy_boolean[1]:
|
if first_found_section == False and hierarchy_boolean[1]:
|
||||||
|
@ -1018,17 +1025,16 @@ def autodoist_magic(args, api, connection):
|
||||||
if first_found_project is False and hierarchy_boolean[2]:
|
if first_found_project is False and hierarchy_boolean[2]:
|
||||||
first_found_project = True
|
first_found_project = True
|
||||||
|
|
||||||
# If there are children
|
# If a parentless or sub-task which has children
|
||||||
if len(child_tasks) > 0:
|
if len(child_tasks) > 0:
|
||||||
|
|
||||||
#TODO: is this still needed?
|
|
||||||
# # Check if task state has changed, if so clean children for good measure
|
# # Check if task state has changed, if so clean children for good measure
|
||||||
# if task_type_changed == 1:
|
# if task_type_changed == 1:
|
||||||
# [remove_label(child_task, next_action_label, overview_task_ids, overview_task_labels)
|
# [remove_label(child_task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
# for child_task in child_tasks]
|
# for child_task in child_tasks]
|
||||||
|
|
||||||
#If a sub-task, inherit parent task type
|
#If it is a sub-task with no own type, inherit the parent task type instead
|
||||||
if task.parent_id != 0:
|
if task.parent_id != 0 and task_type == None:
|
||||||
# dominant_type = task.parent_type # TODO: METADATA
|
# dominant_type = task.parent_type # TODO: METADATA
|
||||||
dominant_type = db_read_value(
|
dominant_type = db_read_value(
|
||||||
connection, task, 'parent_type')[0][0]
|
connection, task, 'parent_type')[0][0]
|
||||||
|
@ -1058,7 +1064,7 @@ def autodoist_magic(args, api, connection):
|
||||||
elif dominant_type == 'parallel' or (dominant_type == 's-p' and next_action_label in task.labels):
|
elif dominant_type == 'parallel' or (dominant_type == 's-p' and next_action_label in task.labels):
|
||||||
remove_label(
|
remove_label(
|
||||||
task, next_action_label, overview_task_ids, overview_task_labels)
|
task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
db_update_value(connection, task, 'task_type', None) #TODO: integrate in remove_label funcionality, else a lot of duplicates. #TODO: None not registered, fix bug.
|
# db_update_value(connection, task, 'task_type', None) #TODO: integrate in remove_label funcionality, else a lot of duplicates.
|
||||||
|
|
||||||
for child_task in child_tasks:
|
for child_task in child_tasks:
|
||||||
|
|
||||||
|
@ -1077,94 +1083,94 @@ def autodoist_magic(args, api, connection):
|
||||||
# Remove labels based on start / due dates
|
# Remove labels based on start / due dates
|
||||||
|
|
||||||
# If task is too far in the future, remove the next_action tag and skip #TODO: FIX THIS
|
# If task is too far in the future, remove the next_action tag and skip #TODO: FIX THIS
|
||||||
try:
|
# try:
|
||||||
if args.hide_future > 0 and 'due' in task.data and task.due is not None:
|
# if args.hide_future > 0 and 'due' in task.data and task.due is not None:
|
||||||
due_date = datetime.strptime(
|
# due_date = datetime.strptime(
|
||||||
task.due['date'][:10], "%Y-%m-%d")
|
# task.due['date'][:10], "%Y-%m-%d")
|
||||||
future_diff = (
|
# future_diff = (
|
||||||
due_date - datetime.today()).days
|
# due_date - datetime.today()).days
|
||||||
if future_diff >= args.hide_future:
|
# if future_diff >= args.hide_future:
|
||||||
remove_label(
|
# remove_label(
|
||||||
task, next_action_label, overview_task_ids, overview_task_labels)
|
# task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
continue
|
# continue
|
||||||
except:
|
# except:
|
||||||
# Hide-future not set, skip
|
# # Hide-future not set, skip
|
||||||
continue
|
# continue
|
||||||
|
|
||||||
# If start-date has not passed yet, remove label
|
# If start-date has not passed yet, remove label #TODO: FIX THIS
|
||||||
try:
|
# try:
|
||||||
f1 = task.content.find('start=')
|
# f1 = task.content.find('start=')
|
||||||
f2 = task.content.find('start=due-')
|
# f2 = task.content.find('start=due-')
|
||||||
if f1 > -1 and f2 == -1:
|
# if f1 > -1 and f2 == -1:
|
||||||
f_end = task.content[f1+6:].find(' ')
|
# f_end = task.content[f1+6:].find(' ')
|
||||||
if f_end > -1:
|
# if f_end > -1:
|
||||||
start_date = task.content[f1 +
|
# start_date = task.content[f1 +
|
||||||
6:f1+6+f_end]
|
# 6:f1+6+f_end]
|
||||||
else:
|
# else:
|
||||||
start_date = task.content[f1+6:]
|
# start_date = task.content[f1+6:]
|
||||||
|
|
||||||
# If start-date hasen't passed, remove all labels
|
# # If start-date hasen't passed, remove all labels
|
||||||
start_date = datetime.strptime(
|
# start_date = datetime.strptime(
|
||||||
start_date, args.dateformat)
|
# start_date, args.dateformat)
|
||||||
future_diff = (
|
# future_diff = (
|
||||||
datetime.today()-start_date).days
|
# datetime.today()-start_date).days
|
||||||
if future_diff < 0:
|
# if future_diff < 0:
|
||||||
remove_label(
|
# remove_label(
|
||||||
task, next_action_label, overview_task_ids, overview_task_labels)
|
# task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
[remove_label(child_task, next_action_label, overview_task_ids,
|
# [remove_label(child_task, next_action_label, overview_task_ids,
|
||||||
overview_task_labels) for child_task in child_tasks]
|
# overview_task_labels) for child_task in child_tasks]
|
||||||
continue
|
# continue
|
||||||
|
|
||||||
except:
|
# except:
|
||||||
logging.warning(
|
# logging.warning(
|
||||||
'Wrong start-date format for task: "%s". Please use "start=<DD-MM-YYYY>"', task.content)
|
# 'Wrong start-date format for task: "%s". Please use "start=<DD-MM-YYYY>"', task.content)
|
||||||
continue
|
# continue
|
||||||
|
|
||||||
# Recurring task friendly - remove label with relative change from due date #TODO Fix this logic
|
# Recurring task friendly - remove label with relative change from due date #TODO FIX THIS
|
||||||
try:
|
# try:
|
||||||
f = task.content.find('start=due-')
|
# f = task.content.find('start=due-')
|
||||||
if f > -1:
|
# if f > -1:
|
||||||
f1a = task.content.find(
|
# f1a = task.content.find(
|
||||||
'd') # Find 'd' from 'due'
|
# 'd') # Find 'd' from 'due'
|
||||||
f1b = task.content.rfind(
|
# f1b = task.content.rfind(
|
||||||
'd') # Find 'd' from days
|
# 'd') # Find 'd' from days
|
||||||
f2 = task.content.find('w')
|
# f2 = task.content.find('w')
|
||||||
f_end = task.content[f+10:].find(' ')
|
# f_end = task.content[f+10:].find(' ')
|
||||||
|
|
||||||
if f_end > -1:
|
# if f_end > -1:
|
||||||
offset = task.content[f+10:f+10+f_end-1]
|
# offset = task.content[f+10:f+10+f_end-1]
|
||||||
else:
|
# else:
|
||||||
offset = task.content[f+10:-1]
|
# offset = task.content[f+10:-1]
|
||||||
|
|
||||||
try:
|
# try:
|
||||||
task_due_date = task.due['date'][:10]
|
# task_due_date = task.due['date'][:10]
|
||||||
task_due_date = datetime.strptime(
|
# task_due_date = datetime.strptime(
|
||||||
task_due_date, '%Y-%m-%d')
|
# task_due_date, '%Y-%m-%d')
|
||||||
except:
|
# except:
|
||||||
logging.warning(
|
# logging.warning(
|
||||||
'No due date to determine start date for task: "%s".', task.content)
|
# 'No due date to determine start date for task: "%s".', task.content)
|
||||||
continue
|
# continue
|
||||||
|
|
||||||
if f1a != f1b and f1b > -1: # To make sure it doesn't trigger if 'w' is chosen
|
# if f1a != f1b and f1b > -1: # To make sure it doesn't trigger if 'w' is chosen
|
||||||
td = timedelta(days=int(offset))
|
# td = timedelta(days=int(offset))
|
||||||
elif f2 > -1:
|
# elif f2 > -1:
|
||||||
td = timedelta(weeks=int(offset))
|
# td = timedelta(weeks=int(offset))
|
||||||
|
|
||||||
# If we're not in the offset from the due date yet, remove all labels
|
# # If we're not in the offset from the due date yet, remove all labels
|
||||||
start_date = task_due_date - td
|
# start_date = task_due_date - td
|
||||||
future_diff = (
|
# future_diff = (
|
||||||
datetime.today()-start_date).days
|
# datetime.today()-start_date).days
|
||||||
if future_diff < 0:
|
# if future_diff < 0:
|
||||||
remove_label(
|
# remove_label(
|
||||||
task, next_action_label, overview_task_ids, overview_task_labels)
|
# task, next_action_label, overview_task_ids, overview_task_labels)
|
||||||
[remove_label(child_task, next_action_label, overview_task_ids,
|
# [remove_label(child_task, next_action_label, overview_task_ids,
|
||||||
overview_task_labels) for child_task in child_tasks]
|
# overview_task_labels) for child_task in child_tasks]
|
||||||
continue
|
# continue
|
||||||
|
|
||||||
except:
|
# except:
|
||||||
logging.warning(
|
# logging.warning(
|
||||||
'Wrong start-date format for task: %s. Please use "start=due-<NUM><d or w>"', task.content)
|
# 'Wrong start-date format for task: %s. Please use "start=due-<NUM><d or w>"', task.content)
|
||||||
continue
|
# continue
|
||||||
|
|
||||||
# Return all ids and corresponding labels that need to be modified
|
# Return all ids and corresponding labels that need to be modified
|
||||||
return overview_task_ids, overview_task_labels
|
return overview_task_ids, overview_task_labels
|
||||||
|
|
Loading…
Reference in New Issue