feat: added impl code to merge from and to grocy

This commit is contained in:
Aerex 2018-12-08 21:19:17 -06:00
parent bf7de34bef
commit 0c187d6684
4 changed files with 195 additions and 28 deletions

View File

@ -1,7 +1,6 @@
import requests import requests
class TaskService(object): class TaskService(object):
def __init__(self, config): def __init__(self, **entries):
self.config = config
def findAll(): def findAll():
raise NotImplementedError() raise NotImplementedError()

View File

@ -1,45 +1,69 @@
from twservices.apps import TaskService from twservices.apps import TaskService
from sys import exit
import twservices.Taskwarrior
from datetime import datetime from datetime import datetime
from twservices.apps import RestService from twservices.apps import RestService
import logging
log = logging.getLogger(__name__)
class Grocy(TaskService):
GROCY_API_KEY_HEADER = 'GROCY_API_KEY' GROCY_API_KEY_HEADER = 'GROCY_API_KEY'
# Endpoints # Endpoints
GROCY_FIND_ALL_TASKS_ENDPOINT = '/get-objects/tasks' GROCY_FIND_ALL_TASKS_ENDPOINT = '/get-objects/tasks'
GROCY_DELTE_TASK_ENDPOINT = '/delete-object/tasks/{0}' GROCY_DELTE_TASK_ENDPOINT = '/delete-object/tasks/{0}'
GROCY_ADD_TASK_ENDPOINT = '/add-object/tasks' GROCY_ADD_TASK_ENDPOINT = '/add-object/tasks'
class Grocy(TaskService):
def __init__(self, config): # UDA
if config['api'] == None: GROCY_ID = 'grocy_id'
raise Exception('api url for Grocy is not defined') UDAS = {
# TODO: check if api is a url GROCY_ID: {
self.api = config['api'] 'type': 'numeric',
'label': 'Grocy Task ID'
}
}
if config['token'] == None: def __init__(self, **entries):
raise Exception('token for Grocy is not defined') super(entries)
self.token = config['token'] self.tw = None
self.rest_service = self.__initRestService()
def __convertToGrocyTask(self, task): def get_udas():
return UDAS
def set_taskwarrior(tw):
self.tw = tw
def get_grocy_tw_tasks():
if(not self.tw):
raise TaskwarriorError('taskwarrior instance not defined')
return tw.filter_tasks({
'and': [('%s.any' % key, None) for key in list(UDA.keys())],
'or': [ ('status', 'pending'), ('status', 'waiting') ]
})
def to_grocy(self, task):
grocy_task = {} grocy_task = {}
grocy_task['id'] = int(task['id'])
if task['description']: if GROCY_ID in task
grocy_task['name'] = str(task['description']) grocy_task['id'] = task[GROCY_ID]
if task['annotations'] if 'description' in task:
grocy_task['name'] = task['description']
if 'annotations' in task:
grocy_task['description'] = '' grocy_task['description'] = ''
for note in task['annotations']: for note in task['annotations']:
grocy_task['description'] += '{}\n'.format(note) grocy_task['description'] += '{}\n'.format(note)
if task['entry'] and type(task['entry']) is datettime: if 'entry' in task and type(task['entry']) is datettime:
grocy_task['row_created_timestamp'] = task['entry'].format('%Y-%m-%d') grocy_task['row_created_timestamp'] = task['entry'].format('%Y-%m-%d')
if task['due'] and type(task['due']) is datetime: if 'due' in task and type(task['due']) is datetime:
grocy_task['due_date'] = task['due'].format('%Y-%m-%d') grocy_task['due_date'] = task['due'].format('%Y-%m-%d')
if task['done']: if 'done' in task:
grocy_task['done'] = task['done'] grocy_task['done'] = task['done']
return grocy_task return grocy_task
@ -86,7 +110,7 @@ class Grocy(TaskService):
try: try:
for next_task in tasks: for next_task in tasks:
self.__convertToGrocyTask(next_task) self.to_grocy(next_task)
response = self.rest_service.post(GROCY_ADD_TASK_ENDPOINT, task) response = self.rest_service.post(GROCY_ADD_TASK_ENDPOINT, task)
responses.append(response) responses.append(response)
except Exception as e: except Exception as e:
@ -105,7 +129,7 @@ class Grocy(TaskService):
tasks = task tasks = task
try: try:
for next_task in tasks: for next_task in tasks:
self.__convertToGrocyTask(next_task) self.to_grocy(next_task)
response = self.rest_service.patch(GROCY_MODIFY_TASK_ENDPOINT, task['id'], task) response = self.rest_service.patch(GROCY_MODIFY_TASK_ENDPOINT, task['id'], task)
responses.append(response) responses.append(response)
except Exception as e: except Exception as e:
@ -113,5 +137,133 @@ class Grocy(TaskService):
raise e raise e
return responses return responses
# TODO: do this part next def sort_by_grocy_id(task_a, task_b):
if 'GROCY_ID' in task_a and 'GROCY_ID' in task_b:
return task_a['GROCY_ID'] - task_b['GROCY_ID']
elif 'id' in task_a and 'id' in task_b:
return task_a['id'] - task_b['id']
else:
return 0
def merge_from_tw(self, taskwarrior_tasks, grocy_tasks):
# clone taskwarrior_tasks for processing
remaining_grocy_tasks_to_process = grocy_tasks[:]
# Convert to taskwarrior tasks into grocy tasks
converted_tw_to_grocy_tasks = []
modified_grocy_tasks = []
for task in taskwarrior_tasks:
converted_tw_to_grocy_tasks.append(self.to_grocy(task))
# Sort grocy tasks and converted taskwarrior tasks by GROCY_ID
converted_tw_to_grocy_tasks.sort(self.sort_by_grocy_id)
grocy_tasks.sort(self.sort_by_grocy_id)
# Iterate through grocy tasks to find any conflicts
for index in range(len(converted_tw_to_grocy_tasks)):
grocy_task = grocy_tasks[index]
converted_tw_to_grocy_task = converted_tw_to_grocy_tasks[index]
finished_merge_process = index > len(remaining_grocy_tasks_to_process) - 1
# Add taskwarrior task to grocy if no more grocy tasks to process
if finished_merge_process:
tasks_to_add_to_grocy.append(converted_tw_to_grocy_task)
log.debug('Added grocy task %s to taskwarrior task', grocy_task['id'])
continue
# Merge from grocy into taskwarrior if match found
# Delete taskwarrior task
if self.should_merge(converted_tw_to_grocy_task, grocy_task):
try:
modified_grocy_tasks.append(converted_tw_to_grocy_task)
log.debug('Merged taskwarrior task %s to grocy task %s', converted_tw_to_grocy_task['uuid'], grocy_task['id'])
del remaining_grocy_tasks_to_process[index]
except TaskwarriorError as e:
log.exception('Could not update task: %s', % e.stderr)
## Add taskwarrior task into taskwarrior
else:
tasks_to_add_to_grocy(converted_tw_to_grocy_task)
log.debug('Added grocy task %s to taskwarrior task', grocy_task['id'])
# Add any remaining taskwarrior tasks not found in grocy into grocy
for index in range(len(converted_tw_to_grocy_tasks)):
tw_task = self.to_taskwarrior(converted_tw_to_grocy_tasks[index])
tw.task_add(tw_task)
# Send requests to Grocy service
self.modify(modified_grocy_tasks)
self.add(tasks_to_add_to_grocy)
def merge_into_tw(self, taskwarrior_tasks, grocy_tasks):
# Convert to taskwarrior tasks into grocy tasks
converted_tw_to_grocy_tasks = []
for task in taskwarrior_tasks:
converted_tw_to_grocy_tasks.append(self.to_grocy(task))
# Sort grocy tasks and converted taskwarrior tasks by GROCY_ID
converted_tw_to_grocy_tasks.sort(self.sort_by_grocy_id)
grocy_tasks.sort(self.sort_by_grocy_id)
# Iterate through grocy tasks to find any conflicts
for index in range(len(grocy_tasks)):
grocy_task = grocy_tasks[index]
converted_tw_to_grocy_task = converted_tw_to_grocy_tasks[index]
current_index_exceeded_tw_list = index > len(converted_tw_to_grocy_tasks) - 1
# Add if no more taskwarrior tasks to process
if current_index_exceeded_tw_list:
try:
tw.task_add(convert_tw_task)
log.debug('Added grocy task %s to taskwarrior task', grocy_task['id'])
except TaskwarriorError as e:
log.exception('Could not add task: %s', % e.stderr)
continue
# Merge from grocy into taskwarrior if match found
# Delete taskwarrior task
if self.should_merge(converted_tw_to_grocy_task, grocy_task):
try:
converted_grocy_task_to_tw = self.to_taskwarrior(grocy_task)
tw.update(converted_grocy_task_to_tw)
log.debug('Merged grocy task %s to taskwarrior task %s', grocy_task['id'], converted_grocy_task_to_tw['uuid'])
del converted_tw_to_grocy_tasks[index]
except TaskwarriorError as e:
log.exception('Could not update task: %s', % e.stderr)
## Add grocy task into taskwarrior
else:
try:
tw.task_add(convert_tw_task)
log.debug('Added grocy task %s to taskwarrior task', grocy_task['id'])
except TaskwarriorError as e:
log.exception('Could not add task: %s', % e.stderr)
# Add any remaining taskwarrior tasks not found in grocy into grocy
for index in range(len(convert_tw_task)):
tw_task = self.to_taskwarrior(converted_tw_to_grocy_tasks[index])
tw.task_add(tw_task)
def sync(): def sync():
# Get all tasks from taskwarrior
# Get all tasks from grocy
# Push to taskwarrior
taskwarrior_tasks = self.get_grocy_tw_tasks()
try:
grocy_tasks = self.findAll()
except BaseException as e:
logger.error(e)
logger.error('Could not get all grocy tasks')
try:
if self.resolution == 'tw':
self.merge_from_tw(taskwarrior_tasks, grocy_tasks)
else:
self.merge_into_tw(taskwarrior_tasks, grocy_task)
except BaseException as e:
print(e)
print('Could not sync')

14
cli.py
View File

@ -1,7 +1,7 @@
import click import click
from pydoc import importfile, ErrorDuringImport from pydoc import importfile, ErrorDuringImport
import yaml import yaml
from site import exit from sys import exit
from shutil import copy from shutil import copy
from os import path, chmod, makedirs from os import path, chmod, makedirs
import stat import stat
@ -57,11 +57,23 @@ def sync():
cfg_file = get_config_file() cfg_file = get_config_file()
fd = open(cfdg_file); fd = open(cfdg_file);
parse_cfg_file = yaml.safe_load(fd) parse_cfg_file = yaml.safe_load(fd)
taskwarrior_cfg = parse_cfg_file['taskwarrior']
services = parse_cfg_file['services'] services = parse_cfg_file['services']
log_cfg = parse_cfg_file['logger']
log_level = 'info' if not 'level'in log_cfg else log_cfg['level']
log_filename = 'log' if not 'file' in log_cfg else log_cfg['file']
logging.basicConfig(level=log_level, filname=log_filename
for service in services: for service in services:
try: try:
ServiceClass = importfile('{}/apps/{}.py'.format(PROJ_DIR, service['name']) ServiceClass = importfile('{}/apps/{}.py'.format(PROJ_DIR, service['name'])
ServiceInstance = ServiceClass(**service) ServiceInstance = ServiceClass(**service)
ServiceClass.setTaskwarrior(tw)
ServiceInstance.sync() ServiceInstance.sync()
except ErrorDuringImport as e: except ErrorDuringImport as e:
print(e) print(e)

View File

@ -1,7 +1,11 @@
logger: logger:
level: debug level: debug
taskwarrior:
rc: ~/.taskrc
services: services:
- name: 'grocy' - name: 'grocy'
api: 'https://aerex.me/grocy/api' api: 'https://aerex.me/grocy/api'
token: 'McaeCf5FrT9Sqr96tPcZg9l4uUCexR1fGVGIfDR6qNQxsWECpv' token: 'McaeCf5FrT9Sqr96tPcZg9l4uUCexR1fGVGIfDR6qNQxsWECpv'
tags:
- grocy