This repository has been archived by the owner on Jul 25, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtask.go
177 lines (151 loc) · 4.46 KB
/
task.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package ticktick
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"net/http"
ess "github.com/unixpickle/essentials"
)
// A Task is a thing that can be done.
type Task struct {
ID string `json:"id,omitempty"`
ProjectID string `json:"projectId,omitempty"`
Title string `json:"title"`
Content string `json:"content,omitempty"`
StartDate string `json:"startDate,omitempty"`
DueDate string `json:"dueDate,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
IsAllDay *bool `json:"isAllDay,omitempty"`
Priority int8 `json:"priority"`
}
// NewTask is a convenience function for creating simple tasks.
func NewTask(title string) *Task {
return &Task{Title: title}
}
const (
// listTasksURL is the URL used for batch listing remaining tasks.
listTasksURL = baseURL + "/batch/check"
// addTaskURL is the URL used for adding a new task.
addTaskURL = baseURL + "/task"
)
// GetTasks returns a lists all remaining (incomplete) TickTick tasks.
func (c *Client) GetTasks() ([]*Task, error) {
var (
url = fmt.Sprintf("%s/%d", listTasksURL, c.checkpoint)
res, err = c.HTTP.Get(url)
)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 { // bad response
return nil, ess.AddCtx("ticktick", errFromRes(res))
}
// Decode response body.
var (
data struct {
Checkpoint uint64 `json:"checkPoint"`
SyncTaskBean struct {
Update []*Task `json:"update"`
} `json:"syncTaskBean"`
}
dec = json.NewDecoder(res.Body)
)
if err = dec.Decode(&data); err != nil {
return nil, ess.AddCtx("ticktick: decoding response body", err)
}
// Close response body.
if err = res.Body.Close(); err != nil {
return nil, err
}
// Update internal checkpoint.
c.checkpoint = data.Checkpoint
c.updateCachedTasks(data.SyncTaskBean.Update)
// Create array of tasks from task cache.
tasks := make([]*Task, 0, len(c.tasks))
for _, task := range c.tasks {
tasks = append(tasks, task)
}
return tasks, nil
}
// AddTask adds a task to TickTick.
//
// Some important fields on 't' to fill out include t.Name and t.ProjectID.
func (c *Client) AddTask(t *Task) (updated *Task, err error) {
// Encode task to JSON, and store in buf.
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
if err := enc.Encode(t); err != nil {
return nil, ess.AddCtx("ticktick: encoding task", err)
}
// Create request.
req, err := http.NewRequest("POST", addTaskURL, buf)
if err != nil {
return nil, ess.AddCtx("ticktick: creating request", err)
}
req.Header.Add("Content-Type", "application/json")
// Perform request.
res, err := c.HTTP.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 { // bad response
return nil, ess.AddCtx("ticktick", errFromRes(res))
}
// Decode response body.
updated = new(Task)
dec := json.NewDecoder(res.Body)
if err = dec.Decode(updated); err != nil {
return nil, ess.AddCtx("ticktick: decoding response body", err)
}
// Close response body.
if err = res.Body.Close(); err != nil {
return nil, err
}
return updated, nil
}
/////////////////////////////
// EXPERIMENTAL / UNSTABLE
/////////////////////////////
// Required for batch adding tasks:
/*
if c.inboxID == "" {
if err := c.checkAccount(); err != nil {
return err
}
if c.inboxID == "" { // checking the account revealed no inbox ID
return errors.New("ticktick: could not determine inbox ID")
}
}
*/
// rateExp is the exponent that controls the rate of change between consecutive
// task IDs; each successive taskID is decreasing by a factor of approximately
// 10^(rateExp + 4).
const rateExp = 19
// nextTaskID generates a new task ID, given the previous task's ID.
//
// It creates an ID string that is less than the previous string, such that
// a task created with this ID will be on the top of the list.
func nextTaskID(prevID string) (string, error) {
intID := new(big.Int)
if _, err := fmt.Sscanf(prevID, "%x", intID); err != nil {
return "", ess.AddCtx("scanning hex ID to big.Int", err)
}
// Maximum for random generation.
exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(rateExp), nil)
max := new(big.Int).Mul(exp, big.NewInt(9995))
// Generate the random part of the ID
random, err := rand.Int(rand.Reader, max)
if err != nil {
return "", ess.AddCtx("generating random ID", err)
}
// Create ID diff.
diff := new(big.Int).Mul(exp, big.NewInt(5))
diff.Add(diff, random)
// Create next ID.
nextID := intID.Sub(intID, diff)
return fmt.Sprintf("%x", nextID), nil
}