feat: added impl code to merge from and to grocy
This commit is contained in:
parent
bf7de34bef
commit
0c187d6684
@ -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()
|
||||||
|
194
apps/grocy.py
194
apps/grocy.py
@ -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
14
cli.py
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user