~nikwen/account-polld/imap-mails

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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
 Copyright 2014 Canonical Ltd.

 This program is free software: you can redistribute it and/or modify it
 under the terms of the GNU General Public License version 3, as published
 by the Free Software Foundation.

 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranties of
 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along
 with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package plugins

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"launchpad.net/account-polld/accounts"
	"launchpad.net/go-xdg/v0"
)

func init() {
	cmdName = filepath.Base(os.Args[0])
}

// Plugin is an interface which the plugins will adhere to for the poll
// daemon to interact with.
//
// Poll interacts with the backend service with the means the plugin defines
// and  returns a list of Notifications to send to the Push service. If an
// error occurs and is returned the daemon can decide to throttle the service.
//
// ApplicationId returns the APP_ID of the delivery target for Post Office.
type Plugin interface {
	ApplicationId() ApplicationId
	Poll(*accounts.AuthData) ([]*PushMessageBatch, error)
}

// AuthTokens is a map with tokens the plugins are to use to make requests.
type AuthTokens map[string]interface{}

// ApplicationId represents the application id to direct posts to.
// e.g.: com.ubuntu.diaspora_diaspora or com.ubuntu.diaspora_diaspora_1.0
type ApplicationId string

// NewStandardPushMessage creates a base Notification with common
// components (members) setup.
func NewStandardPushMessage(summary, body, action, icon string, epoch int64) *PushMessage {
	pm := &PushMessage{
		Notification: Notification{
			Card: &Card{
				Summary:   summary,
				Body:      strings.TrimSpace(body),
				Actions:   []string{action},
				Icon:      icon,
				Timestamp: epoch,
				Popup:     true,
				Persist:   true,
			},
			Sound:   DefaultSound(),
			Vibrate: DefaultVibration(),
			Tag:     cmdName,
		},
	}
	return pm
}

// PushMessageBatch represents a logical grouping of PushMessages that
// have a limit on the number of their notifications that want to be
// presented to the user at the same time, and a way to handle the
// overflow. All Notifications that are part of a Batch share the same
// tag (Tag).  ${Tag}-overflow is the overflow notification tag.
//
// TODO: support notifications sharing just the prefix (so the app can
// tell them apart by tag).
type PushMessageBatch struct {
	Messages        []*PushMessage
	Limit           int
	OverflowHandler func([]*PushMessage) *PushMessage
	Tag             string
}

// PushMessage represents a data structure to be sent over to the
// Post Office. It consists of a Notification and a Message.
type PushMessage struct {
	// Message represents a JSON object that is passed as-is to the
	// application
	Message string `json:"message,omitempty"`
	// Notification (optional) describes the user-facing notifications
	// triggered by this push message.
	Notification Notification `json:"notification,omitempty"`
}

// Notification (optional) describes the user-facing notifications
// triggered by this push message.
type Notification struct {
	// Sound (optional) is the path to a sound file which can or
	// cannot be played depending on user preferences.
	Sound string `json:"sound,omitempty"`
	// Card represents a specific bubble to give to the user
	Card *Card `json:"card,omitempty"`
	// Vibrate is the haptic feedback part of a notification.
	Vibrate *Vibrate `json:"vibrate,omitempty"`
	// EmblemCounter represents and application counter hint
	// related to the notification.
	EmblemCounter *EmblemCounter `json:"emblem-counter,omitempty"`
	// Tag represents a tag to identify persistent notifications
	Tag string `json:"tag,omitempty"`
}

// Card is part of a notification and represents the user visible hints for
// a specific notification.
type Card struct {
	// Summary is a required title. The card will not be presented if this is missing.
	Summary string `json:"summary"`
	// Body is the longer text.
	Body string `json:"body,omitempty"`
	// Whether to show a bubble. Users can disable this, and can easily miss
	// them, so don’t rely on it exclusively.
	Popup bool `json:"popup,omitempty"`
	// Actions provides actions for the bubble's snap decissions.
	Actions []string `json:"actions,omitempty"`
	// Icon is a path to an icon to display with the notification bubble.
	Icon string `json:"icon,omitempty"`
	// Whether to show in notification centre.
	Persist bool `json:"persist,omitempty"`
	// Seconds since the unix epoch, useful for persistent cards.
	Timestamp int64 `json:"Timestamp,omitempty"`
}

// Vibrate is part of a notification and represents the user haptic hints
// for a specific notification.
//
// Duration cannot be used together with Pattern and Repeat.
type Vibrate struct {
	// Duration in milliseconds.
	Duration uint `json:"duration,omitempty"`
	// Pattern is a list of on->off durations that create a pattern.
	Pattern []uint `json:"pattern,omitempty"`
	// Repeat is the number of times a Pattern is to be repeated.
	Repeat uint `json:"repeat,omitempty"`
}

// EmblemCounter is part of a notification and represents the application visual
// hints related to a notification.
type EmblemCounter struct {
	// Count is a number to be displayed over the application’s icon in the
	// launcher.
	Count uint `json:"count"`
	// Visible determines if the counter is visible or not.
	Visible bool `json:"visible"`
}

// The constanst defined here determine the polling aggressivenes with the following criteria
// MAXIMUM: calls, health warning
// HIGH: SMS, chat message, new email
// DEFAULT: social media updates
// LOW: software updates, junk email
const (
	PRIORITY_MAXIMUM = 0
	PRIORITY_HIGH
	PRIORITY_DEFAULT
	PRIORITY_LOW
)

const (
	PLUGIN_EMAIL = 0
	PLUGIN_SOCIAL
)

// ErrTokenExpired is the error returned by a plugin to indicate that
// the web service reported that the authentication token has expired.
var ErrTokenExpired = errors.New("Token expired")

var cmdName string

var XdgDataFind = xdg.Data.Find
var XdgDataEnsure = xdg.Data.Ensure

// Persist stores the plugins data in a common location to a json file
// from which it can recover later
func Persist(pluginName string, accountId uint, data interface{}) (err error) {
	var p string
	defer func() {
		if err != nil && p != "" {
			os.Remove(p)
		}
	}()
	p, err = XdgDataEnsure(filepath.Join(cmdName, fmt.Sprintf("%s-%d.json", pluginName, accountId)))
	if err != nil {
		return err
	}
	file, err := os.Create(p)
	if err != nil {
		return err
	}
	defer file.Close()
	w := bufio.NewWriter(file)
	defer w.Flush()
	jsonWriter := json.NewEncoder(w)
	if err := jsonWriter.Encode(data); err != nil {
		return err
	}
	return nil
}

// FromPersist restores the plugins data from a common location which
// was stored in a json file
func FromPersist(pluginName string, accountId uint, data interface{}) (err error) {
	if reflect.ValueOf(data).Kind() != reflect.Ptr {
		return errors.New("decode target is not a pointer")
	}
	var p string
	defer func() {
		if err != nil {
			if p != "" {
				os.Remove(p)
			}
		}
	}()
	p, err = XdgDataFind(filepath.Join(cmdName, fmt.Sprintf("%s-%d.json", pluginName, accountId)))
	if err != nil {
		return err
	}
	file, err := os.Open(p)
	if err != nil {
		return err
	}
	defer file.Close()
	jsonReader := json.NewDecoder(file)
	if err := jsonReader.Decode(&data); err != nil {
		return err
	}

	return nil
}

// DefaultSound returns the path to the default sound for a Notification
func DefaultSound() string {
	// path is searched within XDG_DATA_DIRS
	return "sounds/ubuntu/notifications/Blip.ogg"
}

// DefaultVibration returns a Vibrate with the default vibration
func DefaultVibration() *Vibrate {
	return &Vibrate{Duration: 200}
}