refactor(add project module folder; added more logs):

This commit is contained in:
Aerex 2018-12-16 02:17:29 -06:00
parent 0c187d6684
commit 38a6fcbed6
10 changed files with 474 additions and 411 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.pyc
log
include/
bin/
lib/
*.egg-info

View File

@ -0,0 +1 @@
.. readme

View File

@ -1,86 +0,0 @@
import requests
class TaskService(object):
def __init__(self, **entries):
def findAll():
raise NotImplementedError()
def modify():
raise NotImplementedError()
class RestService(object):
def __init__(self, api_url, json):
self.api_url = api_url
self.headers = {}
self.json
def get(self, path):
api_url = self.api_url
if api_url.endswith('/'):
url = '{0}{1}'.format(api_url, path)
else
url = '{0}/{1}'.format(api_url, path)
r = requests.get(url, headers=self.headers)
if self.json:
return r.json()
return r.content
def delete(self, id):
api_url = self.api_url
if api_url.endswith('/'):
url = '{0}{1}'.format(api_url, path)
else
url = '{0}/{1}'.format(api_url, path)
r = requests.get(url, headers=self.headers)
if self.json:
return r.json()
return r.content
def post(self, data):
api_url = self.api_url
if api_url.endswith('/'):
url = '{0}{1}'.format(api_url, path)
else
url = '{0}/{1}'.format(api_url, path)
r = requests.post(url, data, headers=self.headers)
if self.json:
return r.json()
return r.content
def addHeader(self, type, value):
self.header[type] = value

View File

@ -1,269 +0,0 @@
from twservices.apps import TaskService
from sys import exit
import twservices.Taskwarrior
from datetime import datetime
from twservices.apps import RestService
import logging
log = logging.getLogger(__name__)
class Grocy(TaskService):
GROCY_API_KEY_HEADER = 'GROCY_API_KEY'
# Endpoints
GROCY_FIND_ALL_TASKS_ENDPOINT = '/get-objects/tasks'
GROCY_DELTE_TASK_ENDPOINT = '/delete-object/tasks/{0}'
GROCY_ADD_TASK_ENDPOINT = '/add-object/tasks'
# UDA
GROCY_ID = 'grocy_id'
UDAS = {
GROCY_ID: {
'type': 'numeric',
'label': 'Grocy Task ID'
}
}
def __init__(self, **entries):
super(entries)
self.tw = None
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 = {}
if GROCY_ID in task
grocy_task['id'] = task[GROCY_ID]
if 'description' in task:
grocy_task['name'] = task['description']
if 'annotations' in task:
grocy_task['description'] = ''
for note in task['annotations']:
grocy_task['description'] += '{}\n'.format(note)
if 'entry' in task and type(task['entry']) is datettime:
grocy_task['row_created_timestamp'] = task['entry'].format('%Y-%m-%d')
if 'due' in task and type(task['due']) is datetime:
grocy_task['due_date'] = task['due'].format('%Y-%m-%d')
if 'done' in task:
grocy_task['done'] = task['done']
return grocy_task
def __initRestService(self):
if self.api.startswith == '/':
api_url = self.api_url[1:]
if self.api.endswith == '/':
api_url =
pruned_api_url = self.api
rest_service = RestService(self.api, json=True)
rest_service.addHeader('Authorization', self.token)
return rest_service
def findAll(self):
# Get all tasks as json
try:
response = self.rest_service.get(GROCY_FIND_ALL_TASKS_ENDPOINT)
except Exception as e:
print(str(e))
raise e
return response
def delete(self, id):
if not id:
raise Exception('id is not defined')
# Delete a task
try:
response = self.rest_service.get(GROCY_DELTE_TASK_ENDPOINT, id)
except Exception as e:
print(str(e))
raise e
return response
def add(self, task):
responses = []
if not task:
raise Exception('task is not defined')
if not type(task) is list:
tasks = [task]
else:
tasks = task
try:
for next_task in tasks:
self.to_grocy(next_task)
response = self.rest_service.post(GROCY_ADD_TASK_ENDPOINT, task)
responses.append(response)
except Exception as e:
print(str(e))
raise e
return responses
def modify(self, task);
response = []
if not task:
raise Exception('task is not defined')
if not type(task) is list:
tasks = [task]
else:
tasks = task
try:
for next_task in tasks:
self.to_grocy(next_task)
response = self.rest_service.patch(GROCY_MODIFY_TASK_ENDPOINT, task['id'], task)
responses.append(response)
except Exception as e:
print(str(e))
raise e
return responses
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():
# 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')

72
cli.py
View File

@ -1,46 +1,43 @@
import click import click
from pydoc import importfile, ErrorDuringImport from pkg_resources import iter_entry_points
import yaml import yaml
from sys 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 from taskw import TaskWarriorShellout
import logging
log = logging.getLogger(__name__)
APP_NAME = 'twservices' APP_NAME = 'twservices'
SAMPLE_CONFIG_FILE = 'sample.config.yml' SAMPLE_CONFIG_FILE = 'sample.config.yml'
CONFIG_FILE = 'config.yml' CONFIG_FILE = 'config.yml'
CONFIG_DIR = click.get_app_dir(APP_NAME) CONFIG_DIR = click.get_app_dir(APP_NAME)
PROJ_DIR = os.path.dirname(os.path.realpath(__file__)) PROJ_DIR = path.join(path.dirname(path.realpath(__file__)), APP_NAME)
def __create_config_file(): def __create_config_file():
user_cfg_file = path.join(CONFIG_DIR, CONFIG_FILE) user_cfg_file = path.join(CONFIG_DIR, CONFIG_FILE)
sample_cfg_file = path.join(PROJ_DIR, SAMPLE_CONFIG) sample_cfg_file = path.join(PROJ_DIR, SAMPLE_CONFIG_FILE)
if not os.path.exists(CONFIG_DIR): if not path.exists(CONFIG_DIR):
print('Config {} director does not exist, create...'.format(CONFIG_DIR)) print('Config {} director does not exist, create...'.format(CONFIG_DIR))
makedirs(CONFIG_DIR) makedirs(CONFIG_DIR)
copy(sample_cfg_file, user_cfg_file) copy(sample_cfg_file, user_cfg_file)
print('Copying {} to {}'.format(sample_cfg_file, user_cfg_file)) print('Copying {} to {}'.format(sample_cfg_file, user_cfg_file))
chmod(user_cfg_file, 0o664) chmod(user_cfg_file, 0o664)
return user_cfg_file return user_cfg_file
@click.command()
@click.group() @click.group()
def main(): def main():
pass pass
def get_config_file()
no_config_msg = 'A config file was not found ' + def get_config_file():
'and will be created under {0}. Is that Ok?'.format(click.get_app_dir(APP_NAME)) no_config_msg = 'A config file was not found'
no_config_msg+= ' and will be created under {}. Is that Ok?'
no_config_msg = no_config_msg.format(click.get_app_dir(APP_NAME))
cfg_file = path.join(click.get_app_dir(APP_NAME), 'config.yml') cfg_file = path.join(click.get_app_dir(APP_NAME), 'config.yml')
if not path.exists(cfg_file): if not path.exists(cfg_file):
create_config_app_dir = click.confirm(no_config_msg) create_config_app_dir = click.confirm(no_config_msg)
@ -51,35 +48,42 @@ def get_config_file()
return cfg_file return cfg_file
@main.command() @main.command()
def sync(): def sync():
cfg_file = get_config_file() cfg_file = get_config_file()
fd = open(cfdg_file); fd = open(cfg_file)
parse_cfg_file = yaml.safe_load(fd) parse_cfg_file = yaml.safe_load(fd)
taskwarrior_cfg = parse_cfg_file['taskwarrior'] taskwarrior_cfg = parse_cfg_file['taskwarrior']
services = parse_cfg_file['services'] services = parse_cfg_file['services']
log_cfg = parse_cfg_file['logger'] log_cfg = parse_cfg_file['logger']
log_level = 'info' if not 'level'in log_cfg else log_cfg['level'] log_level = 'info' if 'level' not in log_cfg else log_cfg['level']
log_filename = 'log' if not 'file' in log_cfg else log_cfg['file'] log_filename = 'log' if 'file'not in log_cfg else log_cfg['file']
logging.basicConfig(level=log_level, filname=log_filename
logging.basicConfig(level=log_level, filename=log_filename)
for service in services: for service in services:
try: try:
ServiceClass = importfile('{}/apps/{}.py'.format(PROJ_DIR, service['name']) entry_service_point = iter_entry_points(group='twservices.apps', name=service['name'])
entry_service_point = next(entry_service_point)
except StopIteration as e:
log.error(e)
log.error('Could not import %s service', services['name'])
try:
ServiceClass = entry_service_point.load()
ServiceInstance = ServiceClass(**service) ServiceInstance = ServiceClass(**service)
ServiceClass.setTaskwarrior(tw) udas = ServiceInstance.get_udas()
tw = TaskWarriorShellout(
config_filename=taskwarrior_cfg['rc'],
config_overrides=udas,
marshal=True,
)
ServiceInstance.set_taskwarrior(tw)
ServiceInstance.sync() ServiceInstance.sync()
except ErrorDuringImport as e: except BaseException as e:
print(e) # TODO: Need to handle base exceptions better
# See if you can print stacktrace
log.exception('Could not sync %s', e)
print(e.stderr)
exit(1) exit(1)

View File

@ -1,4 +0,0 @@
SERVICES = ['grocy']
def getListOfServicesNames():
return SERVICES

View File

@ -1,11 +1,13 @@
logger: logger:
level: debug level: DEBUG
file_location: '~/.config/twservices/log'
taskwarrior: taskwarrior:
rc: ~/.taskrc rc: ~/.taskrc
services: services:
- name: 'grocy' - name: grocy
api: 'https://aerex.me/grocy/api' api: 'https://aerex.me/grocy/api'
token: 'McaeCf5FrT9Sqr96tPcZg9l4uUCexR1fGVGIfDR6qNQxsWECpv' token: 'McaeCf5FrT9Sqr96tPcZg9l4uUCexR1fGVGIfDR6qNQxsWECpv'
resolution: tw
tags: tags:
- grocy - grocy

View File

@ -1,32 +1,37 @@
import os import os
from setuptools import setup from setuptools import setup, find_packages
f = open('README.rst') f = open('README.rst')
long_description = f.read().strip() long_description = f.read().strip()
long_description = long_description.split('readme', 1)[1] long_description = long_description.split('readme', 1)[1]
f.close() f.close()
def read(fname): def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read() return open(os.path.join(os.path.dirname(__file__), fname)).read()
setup( setup(
name = "taskwarrior-services", name='twservices',
version = "0.0.1", version='0.0.1',
author = "Aerex", author='Aerex',
author_email = "aerex@aerex.me", author_email='aerex@aerex.me',
description = ("A plugin to create, delete, and modify tasks across various services "), description=('A plugin to create, delete, and modify tasks across various services'),
keywords = "taskwarrior, grocy", keywords='taskwarrior, grocy',
url = "http://packages.python.org/an_example_pypi_project", url='http://packages.python.org/an_example_pypi_project',
packages=['requests', 'pyyaml', 'taskw', 'pytz', 'click'], packages=find_packages(),
install_requires=['Click'], install_requires=['Click', 'pyyaml', 'requests', 'taskw'],
long_description=read('README'), long_description=read('README.rst'),
classifiers=[ classifiers=[
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Topic :: Utilities", "Topic :: Utilities",
"License :: OSI Approved :: BSD License", "License :: OSI Approved :: BSD License",
], ],
entry_points=" entry_points='''
[console_scripts] [console_scripts]
twservices=cli:main twservices=cli:main
[twservices.apps]
grocy=twservices.apps.grocy:Grocy
''',
) )

View File

@ -0,0 +1,96 @@
import requests
import logging
logger = logging.getLogger(__name__)
class TaskService(object):
def __init__(self, **entries):
self.__dict__.update(entries)
def findAll():
raise NotImplementedError()
def modify():
raise NotImplementedError()
class RestService(object):
def __init__(self, api_url, json=False):
self.api_url = api_url
self.headers = {}
self.json = json
def get(self, path):
if self.api_url.endswith('/'):
url = '{0}{1}'.format(self.api_url, path[1:])
else:
url = '{0}{1}'.format(self.api_url, path)
r = requests.get(url, headers=self.headers)
if r.raise_for_status():
logger.error(r.raise_for_status())
raise r.raise_for_status()
if self.json:
return r.json()
return r.content
def delete(self, path, id):
api_url = self.api_url
if api_url.endswith('/'):
url = '{0}{1}'.format(api_url, path[1:])
else:
url = '{0}/{1}'.format(api_url, path)
r = requests.get(url, headers=self.headers)
if r.raise_for_status():
logger.error(r.raise_for_status())
raise r.raise_for_status()
if self.json:
return r.json()
return r.content
def post(self, path, data):
api_url = self.api_url
print(api_url)
if self.api_url.endswith('/'):
api_url = api_url[1:]
if path.startswith('/'):
url ='{0}{1}'.format(api_url, path)
else:
url = '{0}/{1}'.format(api_url, path)
r = requests.post(url, data, headers=self.headers)
if r.raise_for_status():
logger.error(r.raise_for_status())
raise r.raise_for_status()
if self.json:
return r.json()
return r.content
def addHeader(self, type, value):
self.headers[type] = value

308
twservices/apps/grocy.py Normal file
View File

@ -0,0 +1,308 @@
from taskw.exceptions import TaskwarriorError
from twservices.apps import TaskService
from datetime import datetime
from twservices.apps import RestService
import logging
logger = logging.getLogger(__name__)
class Grocy(TaskService):
API_KEY_HEADER = 'GROCY-API-KEY'
# Endpoints
FIND_ALL_TASKS_ENDPOINT = '/get-objects/tasks'
DELETE_TASK_ENDPOINT = '/delete-object/tasks/{0}'
ADD_TASK_ENDPOINT = '/add-object/tasks'
MODIFY_TASK_ENDPOINT = '/edit-object/tasks'
# UDA
GROCY_ID = 'grocy_id'
UDAS = {
GROCY_ID: {
'type': 'string',
'label': 'Grocy Task ID'
}
}
def __init__(self, **entries):
super().__init__(**entries)
self.tw = None
self.rest_service = self.__initRestService()
def get_udas(self):
return {
'uda': Grocy.UDAS
}
def set_taskwarrior(self, tw):
self.tw = tw
def get_grocy_tw_tasks(self):
if not self.tw:
raise TaskwarriorError('taskwarrior instance not defined')
filters = {
'and': [('%s.any' % key, None) for key in list(Grocy.UDAS.keys())],
'or': [('status', 'pending'), ('status', 'waiting')]
}
tasks = self.tw.filter_tasks(filters)
return tasks
def to_grocy(self, task):
print('before the dawn')
print(task)
grocy_task = {}
if Grocy.GROCY_ID in task:
grocy_task['id'] = task[Grocy.GROCY_ID]
if 'description' in task:
grocy_task['name'] = task['description']
if 'annotations' in task:
grocy_task['description'] = ''
for note in task['annotations']:
grocy_task['description'] += '{}\n'.format(note)
if 'entry' in task and type(task['entry']) is datetime:
grocy_task['row_created_timestamp'] = task['entry'].strftime('%Y-%m-%d %H:%M:%S')
if 'due' in task and type(task['due']) is datetime:
grocy_task['due_date'] = task['due'].strftime('%Y-%m-%d')
if 'done' in task:
grocy_task['done'] = task['done']
return grocy_task
def to_taskwarrior(self, grocy_task):
taskwarrior_task = {}
if 'id' in grocy_task:
taskwarrior_task[Grocy.GROCY_ID] = grocy_task['id']
if 'name' in grocy_task:
taskwarrior_task['description'] = grocy_task['name']
if 'description' in grocy_task:
taskwarrior_task['annotations'] = grocy_task['description'].split('\n')
if 'row_created_timestamp' in grocy_task:
taskwarrior_task['entry'] = grocy_task['row_created_timestamp']
if 'due_date' in grocy_task:
taskwarrior_task['due'] = grocy_task['due_date']
if 'done' in grocy_task and grocy_task['done'] == '1':
taskwarrior_task['status'] = 'completed'
return taskwarrior_task
def __initRestService(self):
if self.api.startswith == '/':
self.api = self.api[1:]
if self.api.endswith == '/':
self.api = self.api[0:-1]
rest_service = RestService(self.api, json=True)
rest_service.addHeader(Grocy.API_KEY_HEADER, self.token)
return rest_service
def findAll(self):
if not self.rest_service:
raise Exception('rest service for grocy is not defined')
# Get all tasks as json
try:
response = self.rest_service.get(Grocy.FIND_ALL_TASKS_ENDPOINT)
except BaseException as e:
logger.error(e)
raise e
return response
def delete(self, id):
if not self.rest_service:
raise Exception('rest service for grocy is not defined')
if not id:
raise Exception('id is not defined')
# Delete a task
try:
response = self.rest_service.get(Grocy.DELETE_TASK_ENDPOINT, id)
except Exception as e:
logger.error(e)
raise e
return response
def add(self, task):
if not self.rest_service:
raise Exception('rest service for grocy is not defined')
responses = []
if not task:
raise Exception('task is not defined')
if not type(task) is list:
tasks = [task]
else:
tasks = task
try:
for next_task in tasks:
self.to_grocy(next_task)
response = self.rest_service.post(Grocy.ADD_TASK_ENDPOINT, task)
responses.append(response)
except Exception as e:
logger.error(e)
raise e
return responses
def modify(self, task):
if not self.rest_service:
raise Exception('rest service for grocy is not defined')
response = []
if not task:
raise Exception('task is not defined')
if not type(task) is list:
tasks = [task]
else:
tasks = task
try:
for next_task in tasks:
modify_endpoint = '{0}/{1}'.format(Grocy.MODIFY_TASK_ENDPOINT, next_task['id'])
response = self.rest_service.post(modify_endpoint, next_task)
except Exception as e:
logger.exception('Could not send post to modify grocy task %s', e.stderr)
def _should_merge(self, converted_tw_to_grocy_task, grocy_task):
return True if converted_tw_to_grocy_task['id'] == grocy_task['id'] else False
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 = []
tasks_to_add_to_grocy = []
converted_tw_to_grocy_tasks = [self.to_grocy(task) for task in taskwarrior_tasks]
# Sort grocy tasks by id and converted taskwarrior tasks by GROCY_ID
grocy_tasks.sort(key=lambda x: x['id'])
converted_tw_to_grocy_tasks.sort(key=lambda x: x['id'])
# If no taskwarrior tasks add grocy tasks to taskwarrior
if len(converted_tw_to_grocy_tasks) == 0:
convert_grocy_tasks_to_tw = [self.to_taskwarrior(grocy_task) for grocy_task in grocy_tasks]
for task in convert_grocy_tasks_to_tw:
try:
added_task = self.tw.task_add(**task)
logger.debug('Added grocy task %s to taskwarrior', task)
except TaskwarriorError as e:
logger.exception('Unable to add task from grocy: %s', e.stderr)
# Iterate through grocy tasks to find any conflicts
for index in range(len(converted_tw_to_grocy_tasks)):
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)
logger.debug('Added grocy task %s to taskwarrior task', grocy_task['id'])
continue
grocy_task = grocy_tasks[index]
converted_tw_to_grocy_task = converted_tw_to_grocy_tasks[index]
# Merge from grocy into taskwarrior if match found
# Delete taskwarrior task
if self._should_merge(converted_tw_to_grocy_task, grocy_task):
modified_grocy_tasks.append(converted_tw_to_grocy_task)
logger.debug('Merged taskwarrior task %s to grocy task %s', converted_tw_to_grocy_task['id'], grocy_task['id'])
del remaining_grocy_tasks_to_process[index]
# Add taskwarrior task into taskwarrior
else:
tasks_to_add_to_grocy.append(converted_tw_to_grocy_task)
logger.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])
try:
self.tw.task_add(**tw_task)
except TaskwarriorError as e:
logger.exception('Could not add task %s', e.stderr)
# Send requests to Grocy service
if len(modified_grocy_tasks) > 0:
self.modify(modified_grocy_tasks)
if len(tasks_to_add_to_grocy) > 0:
self.add(tasks_to_add_to_grocy)
def merge_into_tw(self, taskwarrior_tasks, grocy_tasks):
print('here')
# Convert to taskwarrior tasks into grocy tasks
converted_tw_to_grocy_tasks = []
converted_tw_to_grocy_tasks = [self.to_grocy(task) for task in taskwarrior_tasks]
# Sort grocy tasks by id and converted taskwarrior tasks by GROCY_ID
grocy_tasks.sort(key=lambda x: x['id'])
converted_tw_to_grocy_tasks.sort(key=lambda x: x[Grocy.GROCY_ID])
if len(grocy_tasks) == 0:
self.add(converted_tw_to_grocy_tasks)
# 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:
self.tw.task_add(converted_tw_to_grocy_task)
logger.debug('Added grocy task %s to taskwarrior task', grocy_task['id'])
except TaskwarriorError as e:
logger.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)
self.tw.update(converted_grocy_task_to_tw)
logger.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:
logger.exception('Could not update task: %s', e.stderr)
# Add grocy task into taskwarrior
else:
try:
self.tw.task_add(converted_grocy_task_to_tw)
logger.debug('Added grocy task %s to taskwarrior task', grocy_task['id'])
except TaskwarriorError as e:
logger.exception('Could not add task: %s', e.stderr)
# 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])
self.tw.task_add(tw_task)
def sync(self):
# 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_tasks)
except TaskwarriorError as e:
logger.exception('Could not sync tasks %s', e.stderr)