Passed
Push — master ( 4693c5...c4d205 )
by Alexander
01:43
created

things3.convert_task_to_model()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
"""Simple read-only API for Things 3."""
5
6
from __future__ import print_function
7
8
__author__ = "Alexander Willner"
9
__copyright__ = "2020 Alexander Willner"
10
__credits__ = ["Alexander Willner"]
11
__license__ = "Apache"
12
__version__ = "0.0.1"
13
__maintainer__ = "Alexander Willner"
14
__email__ = "[email protected]"
15
__status__ = "Development"
16
17
import sqlite3
18
from os.path import expanduser
19
from os import environ
20
21
# Basic config
22
FILE_SQLITE = '~/Library/Containers/'\
23
              'com.culturedcode.ThingsMac.beta/Data/Library/'\
24
              'Application Support/Cultured Code/Things/Things.sqlite3'\
25
    if not environ.get('THINGSDB') else environ.get('THINGSDB')
26
TAG_WAITING = "Waiting" if not environ.get('TAG_WAITING') \
27
    else environ.get('TAG_WAITING')
28
TAG_MIT = "MIT" if not environ.get('TAG_MIT') \
29
    else environ.get('TAG_MIT')
30
31
# Basic variables
32
FILE_SQLITE = expanduser(FILE_SQLITE)
33
CURSOR = sqlite3.connect(FILE_SQLITE).cursor()
34
35
# Database layout info
36
TASKTABLE = "TMTask"
37
AREATABLE = "TMArea"
38
TAGTABLE = "TMTag"
39
TASKTAGTABLE = "TMTaskTag"
40
ISNOTTRASHED = "TASK.trashed = 0"
41
ISTRASHED = "TASK.trashed = 1"
42
ISOPEN = "TASK.status = 0"
43
ISNOTSTARTED = "TASK.start = 0"
44
ISCANCELLED = "TASK.status = 2"
45
ISCOMPLETED = "TASK.status = 3"
46
ISSTARTED = "TASK.start = 1"
47
ISPOSTPONED = "TASK.start = 2"
48
ISTASK = "TASK.type = 0"
49
ISPROJECT = "TASK.type = 1"
50
ISHEADING = "TASK.type = 2"
51
ISOPENTASK = ISTASK + " AND " + ISNOTTRASHED + " AND " + ISOPEN
52
53
# Query Index
54
I_UUID = 0
55
I_TITLE = 1
56
I_CONTEXT = 2
57
I_CONTEXT_UUID = 3
58
I_DUE = 4
59
60
# Queries
61
LIST_SOMEDAY = ISOPENTASK + " AND " + ISPOSTPONED + \
62
    " AND TASK.startdate IS NULL AND TASK.recurrenceRule IS NULL" + \
63
    " ORDER BY TASK.duedate DESC, TASK.creationdate DESC"
64
LIST_INBOX = ISOPENTASK + " AND " + ISNOTSTARTED + \
65
    " ORDER BY TASK.duedate DESC , TASK.todayIndex"
66
LIST_ANYTIME = ISOPENTASK + " AND " + ISSTARTED + \
67
    " AND TASK.startdate is NULL" + \
68
    " AND (TASK.area NOT NULL OR TASK.project in (SELECT uuid FROM " + \
69
    TASKTABLE + \
70
    " WHERE uuid=TASK.project AND start=1" + \
71
    " AND trashed=0))" + \
72
    " ORDER BY TASK.duedate DESC , TASK.todayIndex"
73
LIST_TODAY = ISOPENTASK + " AND " + ISSTARTED + \
74
    " AND TASK.startdate is NOT NULL" + \
75
    " ORDER BY TASK.duedate DESC , TASK.todayIndex"
76
LIST_UPCOMING = ISOPENTASK + " AND " + ISPOSTPONED + \
77
    " AND (TASK.startDate NOT NULL OR TASK.recurrenceRule NOT NULL)" + \
78
    " ORDER BY TASK.startdate, TASK.todayIndex"
79
LIST_WAITING = ISOPENTASK + \
80
    " AND TAGS.tags=(SELECT uuid FROM " + TAGTABLE + \
81
    " WHERE title='" + TAG_WAITING + "')" + \
82
    " ORDER BY TASK.duedate DESC , TASK.todayIndex"
83
LIST_MIT = ISOPENTASK + " AND " + ISSTARTED + " AND PROJECT.status = 0 " \
84
    " AND TAGS.tags=(SELECT uuid FROM " + TAGTABLE + \
85
    " WHERE title='" + TAG_MIT + "')" + \
86
    " ORDER BY TASK.duedate DESC , TASK.todayIndex"
87
88
89
def get_inbox():
90
    """Get all tasks from the inbox."""
91
    return get_rows(LIST_INBOX)
92
93
94
def get_today():
95
    """Get all tasks from the today list."""
96
    return get_rows(LIST_TODAY)
97
98
99
def get_not_implemented():
100
    """Not implemented warning."""
101
    return [["0", "not implemented", "no context", "0", "0"]]
102
103
104
def get_rows(sql):
105
    """Query Things database."""
106
107
    sql = """
108
        SELECT DISTINCT
109
            TASK.uuid,
110
            TASK.title,
111
            CASE
112
                WHEN AREA.title IS NOT NULL THEN AREA.title
113
                WHEN PROJECT.title IS NOT NULL THEN PROJECT.title
114
                WHEN HEADING.title IS NOT NULL THEN HEADING.title
115
            END,
116
            CASE
117
                WHEN AREA.uuid IS NOT NULL THEN AREA.uuid
118
                WHEN PROJECT.uuid IS NOT NULL THEN PROJECT.uuid
119
            END,
120
            CASE
121
                WHEN TASK.recurrenceRule IS NULL
122
                THEN date(TASK.dueDate,"unixepoch")
123
            ELSE NULL
124
            END
125
        FROM
126
            TMTask AS TASK
127
        LEFT JOIN
128
            TMTaskTag TAGS ON TAGS.tasks = TASK.uuid
129
        LEFT OUTER JOIN
130
            TMTask PROJECT ON TASK.project = PROJECT.uuid
131
        LEFT OUTER JOIN
132
            TMArea AREA ON TASK.area = AREA.uuid
133
        LEFT OUTER JOIN
134
            TMTask HEADING ON TASK.actionGroup = HEADING.uuid
135
        WHERE """ + sql
136
    CURSOR.execute(sql)
137
    return CURSOR.fetchall()
138
139
140
def convert_task_to_model(task):
141
    """Convert task to model."""
142
    model = {'uuid': task[I_UUID],
143
             'title': task[I_TITLE],
144
             'context': task[I_CONTEXT],
145
             'context_uuid': task[I_CONTEXT_UUID],
146
             'due': task[I_DUE],
147
             }
148
    return model
149
150
151
def convert_tasks_to_model(tasks):
152
    """Convert tasks to model."""
153
    model = []
154
    for task in tasks:
155
        model.append(convert_task_to_model(task))
156
    return model
157