-
Notifications
You must be signed in to change notification settings - Fork 1
/
feedcontroller.vala
321 lines (277 loc) · 9.42 KB
/
feedcontroller.vala
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
namespace GmailFeed {
/**
* Types of actions that we want the feed to perfom
**/
internal enum FeedActionType {
READ,
STAR,
UNSTAR,
ARCHIVE,
SPAM,
TRASH,
UPDATE,
LOGIN,
LOGOUT,
SET_OAUTH_ID,
AUTHORIZE,
SET_AUTH_CODE,
QUIT
}
/**
* An action has an id for the message to act on and the type of action that should be taken
**/
internal class FeedAction : GLib.Object {
public string id {get; set; default = "";}
public FeedActionType action {get; set;}
}
internal class OAuthIdAction : FeedAction {
public string clientId {get; set; default = "";}
public string clientSecret {get; set; default = "";}
}
/**
* FeedController exists because we need a way to separate the GUI thread and the message thread.
* We use an async queue to get actions to the message thread then use Idle callbacks to
* get response signals back on the GUI thread.
**/
public class FeedController : GLib.Object {
public signal void newMessage(GMessage msg);
public signal void updatedMessage(GMessage msg);
public signal void messageStarred(string id);
public signal void messageUnstarred(string id);
public signal void messageRemoved(string id);
public signal void messageRead(string id);
public signal void messageArchived(string id);
public signal void messageTrashed(string id);
public signal void messageSpammed(string id);
public signal void loginSuccess();
public signal void loggedOut();
public signal void feedClosed();
public signal void updateComplete();
public signal void feedError(AuthError error);
public signal void requestAuthCode(string url);
/**
* Our feed object. The queue we use to go between threads, and the thread the feed runs on.
**/
private Feed? feed;
private AsyncQueue<FeedAction> queue;
private Thread<void*> thread;
/**
* Create the feed and the feed thread and start them running
**/
public FeedController() {
this.feed = null;
this.queue = new AsyncQueue<FeedAction>();
this.thread = new Thread<void*>("Feed thread", run);
}
protected void createFeed(string address) {
this.feed = new Feed(address);
this.feed.loadInfo();
connectSignals();
var res = this.feed.update();
if(res == AuthError.SUCCESS) {
Idle.add(() => {
this.loginSuccess();
return false;
});
}
}
protected void destroyFeed() {
//disconnectSignals();
this.feed = null;
}
/**
* Take items off the queue, perform the specified action, repeat.
* Responses happen as signals so we don't need to worry about them here.
**/
private void *run() {
while(true) {
var data = queue.pop();
var s = data.id;
switch(data.action) {
case FeedActionType.READ : this.wrapError(this.feed.markRead(s)); break;
case FeedActionType.STAR : this.wrapError(this.feed.starMsg(s)); break;
case FeedActionType.UNSTAR : this.wrapError(this.feed.unstarMsg(s)); break;
case FeedActionType.ARCHIVE : this.wrapError(this.feed.archive(s)); break;
case FeedActionType.SPAM : this.wrapError(this.feed.spam(s)); break;
case FeedActionType.TRASH : this.wrapError(this.feed.trash(s)); break;
case FeedActionType.UPDATE : this.feed.update(); break;
case FeedActionType.LOGIN :
this.createFeed(s);
break;
case FeedActionType.SET_OAUTH_ID :
var id_act = data as OAuthIdAction;
var result = this.feed.setOAuthInfo(id_act.clientId,
id_act.clientSecret);
this.wrapError(result);
break;
case FeedActionType.AUTHORIZE :
var url = this.feed.getAuthUrl();
Idle.add(() => {
this.requestAuthCode(url);
return false;
});
break;
case FeedActionType.SET_AUTH_CODE:
var result = this.feed.setAuthCode(s);
if(result == AuthError.SUCCESS) {
this.feed.update();
} else {
wrapError(result);
}
break;
case FeedActionType.LOGOUT:
this.destroyFeed();
break;
case FeedActionType.QUIT :
Idle.add(() => {
this.feedClosed();
return false;
});
return null;
}
}
}
/**
* Queues an action of the given type, with the optional id
**/
private void pushAction(FeedActionType type, string id = "") {
var act = new FeedAction();
act.id = id;
act.action = type;
queue.push(act);
var queue_length = queue.length();
if (queue_length > 0) {
message("Current action queue length: %u", queue_length);
}
}
/**
* To shutdown we need to get the message thread to stop. We want to let any queued
* actions complete first though.
**/
public void shutdown() {
this.pushAction(FeedActionType.QUIT);
}
/**
* We need to get signals onto a different thread. We do this by adding Idle callbacks
* with the same content which will run on the GUI thread.
**/
private void connectSignals() {
this.feed.newMessage.connect((m) => {
Idle.add(() => {
this.newMessage(new GMessage.copy(m));
return false;
});
});
this.feed.updatedMessage.connect((m) => {
Idle.add(() => {
this.updatedMessage(new GMessage.copy(m));
return false;
});
});
this.feed.messageStarred.connect((m) => {
Idle.add(() => {
this.messageStarred(m);
return false;
});
});
this.feed.messageUnstarred.connect((m) => {
Idle.add(() => {
this.messageUnstarred(m);
return false;
});
});
this.feed.messageArchived.connect((m) => {
Idle.add(() => {
this.messageArchived(m);
return false;
});
});
this.feed.messageTrashed.connect((m) => {
Idle.add(() => {
this.messageTrashed(m);
return false;
});
});
this.feed.messageSpammed.connect((m) => {
Idle.add(() => {
this.messageSpammed(m);
return false;
});
});
this.feed.messageRead.connect((m) => {
Idle.add(() => {
this.messageRead(m);
return false;
});
});
this.feed.messageRemoved.connect((m) => {
Idle.add(() => {
this.messageRemoved(m);
return false;
});
});
this.feed.updateComplete.connect((res) => {
Idle.add(() => {
if(res == AuthError.SUCCESS) {
this.updateComplete();
} else {
this.wrapError(res);
}
return false;
});
});
}
/**
* These methods take care of getting the correct info into the queue to complete the
* desired actions.
**/
public void update() {
this.pushAction(FeedActionType.UPDATE);
}
public void markRead(string id) {
this.pushAction(FeedActionType.READ, id);
}
public void starMsg(string id) {
this.pushAction(FeedActionType.STAR, id);
}
public void unstarMsg(string id) {
this.pushAction(FeedActionType.UNSTAR, id);
}
public void archive(string id) {
this.pushAction(FeedActionType.ARCHIVE, id);
}
public void trash(string id) {
this.pushAction(FeedActionType.TRASH, id);
}
public void spam(string id) {
this.pushAction(FeedActionType.SPAM, id);
}
public void login(string address) {
this.pushAction(FeedActionType.LOGIN, address);
}
public void setOAuthId(string clientId, string clientSecret) {
var action = new OAuthIdAction();
action.clientId = clientId;
action.clientSecret = clientSecret;
action.action = FeedActionType.SET_OAUTH_ID;
queue.push(action);
}
public void getAuthCode() {
this.pushAction(FeedActionType.AUTHORIZE, "");
}
public void setAuthCode(string code) {
this.pushAction(FeedActionType.SET_AUTH_CODE, code);
}
public void logout() {
this.pushAction(FeedActionType.LOGOUT, "");
}
protected void wrapError(AuthError error) {
if(error != AuthError.SUCCESS) {
Idle.add(() => {
this.feedError(error);
return false;
});
}
}
}
}