gntplib 0.5__py3-none-any.whl → 0.66__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of gntplib might be problematic. Click here for more details.
- gntplib/__init__.py +973 -959
- gntplib/__version__.py +1 -0
- gntplib/async.py +193 -193
- gntplib/ciphers.py +97 -97
- gntplib/compat.py +9 -9
- gntplib/exceptions.py +5 -5
- gntplib/keys.py +79 -79
- gntplib/utils.py +11 -11
- {gntplib-0.5.dist-info → gntplib-0.66.dist-info}/METADATA +118 -120
- gntplib-0.66.dist-info/RECORD +12 -0
- {gntplib-0.5.dist-info → gntplib-0.66.dist-info}/WHEEL +5 -5
- gntplib-0.5.dist-info/RECORD +0 -11
- {gntplib-0.5.dist-info → gntplib-0.66.dist-info}/top_level.txt +0 -0
gntplib/__init__.py
CHANGED
|
@@ -1,959 +1,973 @@
|
|
|
1
|
-
"""This module provides core functionalities of gntplib.
|
|
2
|
-
|
|
3
|
-
gntplib is a Growl Notification Transport Protocol (GNTP_) client library in
|
|
4
|
-
Python.
|
|
5
|
-
|
|
6
|
-
.. _GNTP: http://www.growlforwindows.com/gfw/help/gntp.aspx
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import unicode_literals
|
|
10
|
-
import hashlib
|
|
11
|
-
import io
|
|
12
|
-
import re
|
|
13
|
-
import socket
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from .
|
|
17
|
-
from .
|
|
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
|
-
:param
|
|
46
|
-
:param
|
|
47
|
-
:param
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
publisher
|
|
51
|
-
publisher.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
warnings
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
:param
|
|
67
|
-
:param
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
:param
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
subscriber
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
:param
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
:class:`Event` instance
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
:param
|
|
118
|
-
:param
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
:param
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
self.
|
|
133
|
-
self.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
self.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
:param
|
|
157
|
-
:param
|
|
158
|
-
:param
|
|
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
|
-
self.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
warnings
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
warnings
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
:param
|
|
233
|
-
:param
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
:param
|
|
238
|
-
:param
|
|
239
|
-
:param
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
self.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
self.
|
|
256
|
-
self.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
self.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
ttl
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
:param
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
self.
|
|
300
|
-
self.
|
|
301
|
-
self.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
self.
|
|
312
|
-
self.
|
|
313
|
-
self.
|
|
314
|
-
self.
|
|
315
|
-
self.
|
|
316
|
-
self.
|
|
317
|
-
self.
|
|
318
|
-
self.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
:param
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
self.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
:param
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
:param
|
|
355
|
-
:param
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
self
|
|
366
|
-
self.
|
|
367
|
-
self.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
:param
|
|
379
|
-
:param
|
|
380
|
-
:param
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
self
|
|
389
|
-
self.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
:param
|
|
401
|
-
:param
|
|
402
|
-
:param
|
|
403
|
-
:param
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
self
|
|
412
|
-
self.
|
|
413
|
-
self.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
self.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
self.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
the
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
message
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
self.sock
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
:param
|
|
511
|
-
:param
|
|
512
|
-
:param
|
|
513
|
-
:param
|
|
514
|
-
|
|
515
|
-
`keys.
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
`ciphers.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
`
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
self.
|
|
531
|
-
self.
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
conn.
|
|
550
|
-
conn.
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
self.
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
:param
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
self.
|
|
681
|
-
self.
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
def
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
"""
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
LINE_DELIMITER
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
self.write(
|
|
866
|
-
self.write(b'Notification-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
self.
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
self.write(b'
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
self.write(b'Notification-
|
|
882
|
-
self.write(b'Notification-
|
|
883
|
-
self.write(b'Notification-
|
|
884
|
-
self.write(b'Notification-
|
|
885
|
-
self.write(b'Notification-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
def
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
self.writer.write(
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
def
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
def
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
self.
|
|
953
|
-
self.writer.write(
|
|
954
|
-
self.writer.write(b'
|
|
955
|
-
self.writer.write(
|
|
956
|
-
self.writer.write(LINE_DELIMITER)
|
|
957
|
-
self.writer.write(
|
|
958
|
-
self.writer.write(data)
|
|
959
|
-
self.writer.write(
|
|
1
|
+
"""This module provides core functionalities of gntplib.
|
|
2
|
+
|
|
3
|
+
gntplib is a Growl Notification Transport Protocol (GNTP_) client library in
|
|
4
|
+
Python.
|
|
5
|
+
|
|
6
|
+
.. _GNTP: http://www.growlforwindows.com/gfw/help/gntp.aspx
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import unicode_literals
|
|
10
|
+
import hashlib
|
|
11
|
+
import io
|
|
12
|
+
import re
|
|
13
|
+
import socket
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from . import keys
|
|
17
|
+
from .compat import text_type
|
|
18
|
+
from .exceptions import GNTPError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__version__ = '0.5'
|
|
22
|
+
__all__ = ['notify', 'publish', 'subscribe', 'Event', 'Notifier', 'Publisher',
|
|
23
|
+
'RawIcon', 'Resource', 'SocketCallback', 'Subscriber']
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
SUPPORTED_VERSIONS = ['1.0']
|
|
27
|
+
DEFAULT_PORT = 23053
|
|
28
|
+
DEFAULT_TIMEOUT = 10
|
|
29
|
+
DEFAULT_TTL = 60
|
|
30
|
+
MAX_MESSAGE_SIZE = 4096
|
|
31
|
+
MAX_LINE_SIZE = 1024
|
|
32
|
+
LINE_DELIMITER = b'\r\n'
|
|
33
|
+
SECTION_DELIMITER = SECTION_BODY_START = SECTION_BODY_END = b'\r\n'
|
|
34
|
+
MESSAGE_DELIMITER = b'\r\n\r\n'
|
|
35
|
+
MESSAGE_DELIMITER_SIZE = len(MESSAGE_DELIMITER)
|
|
36
|
+
RESPONSE_INFORMATION_LINE_RE = re.compile(
|
|
37
|
+
b'GNTP/([^ ]+) (-OK|-ERROR|-CALLBACK) NONE')
|
|
38
|
+
CUSTOM_HEADER_PREFIX = 'X-'
|
|
39
|
+
APP_SPECIFIC_HEADER_PREFIX = 'Data-'
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def publish(app_name, event_name, title, text=''):
|
|
43
|
+
"""Register a publisher and send a notification at a time.
|
|
44
|
+
|
|
45
|
+
:param app_name: the name of the application.
|
|
46
|
+
:param event_name: the name of the notification.
|
|
47
|
+
:param title: the title of the notification.
|
|
48
|
+
:param text: the text of the notification. Defaults to ``''``.
|
|
49
|
+
"""
|
|
50
|
+
publisher = Publisher(app_name, coerce_to_events([event_name]))
|
|
51
|
+
publisher.register()
|
|
52
|
+
publisher.publish(event_name, title, text)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def notify(app_name, event_name, title, text=''):
|
|
56
|
+
"""Deprecated notify function."""
|
|
57
|
+
import warnings
|
|
58
|
+
warnings.warn('notify function is deprecated, use publish function'
|
|
59
|
+
' instead', DeprecationWarning, stacklevel=2)
|
|
60
|
+
publish(app_name, event_name, title, text)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def subscribe(id_, name, hub, password, port=DEFAULT_PORT):
|
|
64
|
+
"""Send a subscription request and return ttl.
|
|
65
|
+
|
|
66
|
+
:param id_: the unique id of the subscriber.
|
|
67
|
+
:param name: the name of the subscriber.
|
|
68
|
+
:param hub: the subscribed-to machine. If a string is given, it is used as
|
|
69
|
+
a host of the hub and default port number `23053` is used.
|
|
70
|
+
If host-port tuple is given, it is used directly.
|
|
71
|
+
:param password: the password of the subscribed-to machine.
|
|
72
|
+
:param port: the port number of the subscriber. Defaults to `23053`.
|
|
73
|
+
"""
|
|
74
|
+
subscriber = Subscriber(id_, name, hub, password, port=port)
|
|
75
|
+
subscriber.subscribe()
|
|
76
|
+
return subscriber.ttl
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BaseApp(object):
|
|
80
|
+
"""Base class for applications.
|
|
81
|
+
|
|
82
|
+
:param custom_headers: the list of key-value tuples for Custom Headers.
|
|
83
|
+
:param app_specific_headers: the list of key-value tuples for App-Specific
|
|
84
|
+
Headers.
|
|
85
|
+
:param gntp_client_class: GNTP client class. If it is `None`,
|
|
86
|
+
:class:`GNTPClient` is used. Defaults to `None`.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, custom_headers=None, app_specific_headers=None,
|
|
90
|
+
gntp_client_class=None, **kwargs):
|
|
91
|
+
self.custom_headers = custom_headers or []
|
|
92
|
+
self.app_specific_headers = app_specific_headers or []
|
|
93
|
+
if gntp_client_class is None:
|
|
94
|
+
gntp_client_class = GNTPClient
|
|
95
|
+
self.gntp_client = gntp_client_class(**kwargs)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Publisher(BaseApp):
|
|
99
|
+
"""Publisher of Growl Notification Transport Protocol (GNTP).
|
|
100
|
+
|
|
101
|
+
This class supports ``REGISTER`` and ``NOTIFY`` requests. They
|
|
102
|
+
are sent by :meth:`register()` and :meth:`publish()` methods respectively.
|
|
103
|
+
These methods can accept the optional final callback as `callback` keyword
|
|
104
|
+
argument, which run after closing the connection with the GNTP server.
|
|
105
|
+
|
|
106
|
+
`event_defs` is a list of ``str``, ``unicode``, double (of ``str`` and
|
|
107
|
+
``bool``) or :class:`Event` instance. It is converted to a list of
|
|
108
|
+
:class:`Event` instance as follows:
|
|
109
|
+
``str`` or ``unicode`` item becomes value of the `name` attribute of
|
|
110
|
+
:class:`Event` instance, whose other attributes are defaults. Double item
|
|
111
|
+
is expanded to (`name`, `enabled`) tuple, and those values are passed to
|
|
112
|
+
:class:`Event` constructor. :class:`Event` instance item is used directly.
|
|
113
|
+
|
|
114
|
+
Optional keyword arguments are passed to the `gntp_client_class`
|
|
115
|
+
constructor.
|
|
116
|
+
|
|
117
|
+
:param name: the name of the application.
|
|
118
|
+
:param event_defs: the definitions of the notifications.
|
|
119
|
+
:param icon: url string or an instance of :class:`Resource` for the icon of
|
|
120
|
+
the application. Defaults to `None`.
|
|
121
|
+
:param custom_headers: the list of key-value tuples for Custom Headers.
|
|
122
|
+
:param app_specific_headers: the list of key-value tuples for App-Specific
|
|
123
|
+
Headers.
|
|
124
|
+
:param gntp_client_class: GNTP client class. If it is `None`,
|
|
125
|
+
:class:`GNTPClient` is used. Defaults to `None`.
|
|
126
|
+
|
|
127
|
+
.. note:: In Growl 1.3.3, `icon` of url string does not work.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(self, name, event_defs, icon=None, custom_headers=None,
|
|
131
|
+
app_specific_headers=None, gntp_client_class=None, **kwargs):
|
|
132
|
+
self.name = name
|
|
133
|
+
self.icon = icon
|
|
134
|
+
self.events = coerce_to_events(event_defs)
|
|
135
|
+
if not self.events:
|
|
136
|
+
raise GNTPError('You have to set at least one notification type')
|
|
137
|
+
BaseApp.__init__(self, custom_headers, app_specific_headers,
|
|
138
|
+
gntp_client_class, **kwargs)
|
|
139
|
+
|
|
140
|
+
def register(self, callback=None):
|
|
141
|
+
"""Register this publisher to the GNTP server.
|
|
142
|
+
|
|
143
|
+
:param callback: the callback run after closing the connection with
|
|
144
|
+
the GNTP server. Defaults to `None`.
|
|
145
|
+
"""
|
|
146
|
+
request = RegisterRequest(self.name, self.icon, self.events,
|
|
147
|
+
self.custom_headers,
|
|
148
|
+
self.app_specific_headers)
|
|
149
|
+
self.gntp_client.process_request(request, callback)
|
|
150
|
+
|
|
151
|
+
def publish(self, name, title, text='', id_=None, sticky=False,
|
|
152
|
+
priority=0, icon=None, coalescing_id=None, callback=None,
|
|
153
|
+
gntp_callback=None, **socket_callback_options):
|
|
154
|
+
"""Send a notification to the GNTP server.
|
|
155
|
+
|
|
156
|
+
:param name: the name of the notification.
|
|
157
|
+
:param title: the title of the notification.
|
|
158
|
+
:param text: the text of the notification. Defaults to `''`.
|
|
159
|
+
:param id_: the unique ID for the notification. If set, this should be
|
|
160
|
+
unique for every request. Defaults to `None`.
|
|
161
|
+
:param sticky: if set to `True`, the notification remains displayed
|
|
162
|
+
until dismissed by the user. Defaults to `False`.
|
|
163
|
+
:param priority: the display hint for the receiver which may be
|
|
164
|
+
ignored. A higher number indicates a higher priority.
|
|
165
|
+
Valid values are between -2 and 2, defaults to `0`.
|
|
166
|
+
:param icon: url string or an instance of :class:`Resource` to display
|
|
167
|
+
with the notification. Defaults to `None`.
|
|
168
|
+
:param coalescing_id: if set, should contain the value of the `id_` of
|
|
169
|
+
a previously-sent notification.
|
|
170
|
+
This serves as a hint to the notification system
|
|
171
|
+
that this notification should replace/update the
|
|
172
|
+
matching previous notification. The notification
|
|
173
|
+
system may ignore this hint. Defaults to `None`.
|
|
174
|
+
:param callback: the callback run after closing the connection with
|
|
175
|
+
the GNTP server. Defaults to `None`.
|
|
176
|
+
:param gntp_callback: url string for url callback or
|
|
177
|
+
:class:`SocketCallback` instance for socket
|
|
178
|
+
callback. Defaults to `None`.
|
|
179
|
+
:param socket_callback_options: the keyword arguments to be used to
|
|
180
|
+
instantiating :class:`SocketCallback`
|
|
181
|
+
for socket callback. About acceptable
|
|
182
|
+
keyword arguments,
|
|
183
|
+
see :class:`SocketCallback`.
|
|
184
|
+
|
|
185
|
+
.. note:: In Growl 1.3.3, `icon` of url string does not work.
|
|
186
|
+
|
|
187
|
+
.. note:: Growl for Windows v2.0+ and Growl v1.3+ require
|
|
188
|
+
`coalescing_id` to be the same on both the original and
|
|
189
|
+
updated notifcation, ignoring the value of `id_`.
|
|
190
|
+
"""
|
|
191
|
+
notification = Notification(name, title, text, id_=id_, sticky=sticky,
|
|
192
|
+
priority=priority, icon=icon,
|
|
193
|
+
coalescing_id=coalescing_id,
|
|
194
|
+
gntp_callback=gntp_callback,
|
|
195
|
+
**socket_callback_options)
|
|
196
|
+
request = NotifyRequest(self.name, notification,
|
|
197
|
+
self.custom_headers,
|
|
198
|
+
self.app_specific_headers)
|
|
199
|
+
self.gntp_client.process_request(
|
|
200
|
+
request, callback, socket_callback=notification.socket_callback)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class Notifier(Publisher):
|
|
204
|
+
"""Deprecated Notifier of Growl Notification Transport Protocol (GNTP)."""
|
|
205
|
+
|
|
206
|
+
def __init__(self, name, event_defs, icon=None, custom_headers=None,
|
|
207
|
+
app_specific_headers=None, gntp_client_class=None, **kwargs):
|
|
208
|
+
import warnings
|
|
209
|
+
warnings.warn('Notifier is deprecated, use Publisher instead',
|
|
210
|
+
DeprecationWarning, stacklevel=2)
|
|
211
|
+
Publisher.__init__(self, name, event_defs, icon,
|
|
212
|
+
custom_headers, app_specific_headers,
|
|
213
|
+
gntp_client_class, **kwargs)
|
|
214
|
+
|
|
215
|
+
def notify(self, name, title, text='', id_=None, sticky=False,
|
|
216
|
+
priority=0, icon=None, coalescing_id=None, callback=None,
|
|
217
|
+
gntp_callback=None, **socket_callback_options):
|
|
218
|
+
"""Send a notification to the GNTP server."""
|
|
219
|
+
import warnings
|
|
220
|
+
warnings.warn('notify method is deprecated, use publish method'
|
|
221
|
+
' instead', DeprecationWarning, stacklevel=2)
|
|
222
|
+
self.publish(name, title, text, id_, sticky, priority, icon,
|
|
223
|
+
coalescing_id, callback, gntp_callback,
|
|
224
|
+
**socket_callback_options)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class Subscriber(BaseApp):
|
|
228
|
+
"""Subscriber of Growl Notification Transport Protocol (GNTP).
|
|
229
|
+
|
|
230
|
+
This class supports ``SUBSCRIBE`` request.
|
|
231
|
+
|
|
232
|
+
:param id_: the unique id of the subscriber.
|
|
233
|
+
:param name: the name of the subscriber.
|
|
234
|
+
:param hub: the subscribed-to machine. If a string is given, it is used as
|
|
235
|
+
a host of the hub and default port number `23053` is used.
|
|
236
|
+
If host-port tuple is given, it is used directly.
|
|
237
|
+
:param password: the password of the subscribed-to machine.
|
|
238
|
+
:param port: the port number of the subscriber. Defaults to `23053`.
|
|
239
|
+
:param custom_headers: the list of key-value tuples for Custom Headers.
|
|
240
|
+
:param app_specific_headers: the list of key-value tuples for App-Specific
|
|
241
|
+
Headers.
|
|
242
|
+
:param gntp_client_class: GNTP client class. If it is `None`,
|
|
243
|
+
:class:`GNTPClient` is used. Defaults to `None`.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
def __init__(self, id_, name, hub, password, port=DEFAULT_PORT,
|
|
247
|
+
custom_headers=None, app_specific_headers=None,
|
|
248
|
+
gntp_client_class=None, **kwargs):
|
|
249
|
+
self.id_ = id_
|
|
250
|
+
self.name = name
|
|
251
|
+
if isinstance(hub, (bytes, text_type)):
|
|
252
|
+
self.hub = (hub, DEFAULT_PORT)
|
|
253
|
+
else:
|
|
254
|
+
self.hub = hub
|
|
255
|
+
self.password = password
|
|
256
|
+
self.port = port
|
|
257
|
+
self.ttl = DEFAULT_TTL
|
|
258
|
+
BaseApp.__init__(self, custom_headers, app_specific_headers,
|
|
259
|
+
gntp_client_class, host=self.hub[0], port=self.hub[1],
|
|
260
|
+
password=self.password, **kwargs)
|
|
261
|
+
|
|
262
|
+
def subscribe(self, callback=None):
|
|
263
|
+
"""Send a subscription request.
|
|
264
|
+
|
|
265
|
+
If `callback` is `None`, :meth:`store_ttl` is used and :attr:`ttl` is
|
|
266
|
+
updated by ``Subscription-TTL`` value of the response.
|
|
267
|
+
|
|
268
|
+
:param callback: the callback run after closing the connection with
|
|
269
|
+
the GNTP server. Defaults to `None`.
|
|
270
|
+
"""
|
|
271
|
+
request = SubscribeRequest(self.id_, self.name, self.port,
|
|
272
|
+
self.custom_headers,
|
|
273
|
+
self.app_specific_headers)
|
|
274
|
+
self.gntp_client.process_request(request, callback or self.store_ttl)
|
|
275
|
+
|
|
276
|
+
def store_ttl(self, response):
|
|
277
|
+
"""Update :attr:`ttl` attribute."""
|
|
278
|
+
ttl = response.headers['Subscription-TTL']
|
|
279
|
+
self.ttl = int(ttl)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Event(object):
|
|
283
|
+
"""Represent notification type.
|
|
284
|
+
|
|
285
|
+
:param name: the name of the notification.
|
|
286
|
+
:param display_name: the display name of the notification, which is
|
|
287
|
+
appeared at the Applications tab of the Growl
|
|
288
|
+
Preferences. Defaults to `None`.
|
|
289
|
+
:param enabled: indicates if the notification should be enabled by
|
|
290
|
+
default. Defaults to `True`.
|
|
291
|
+
:param icon: url string or an instance of :class:`Resource` for the default
|
|
292
|
+
icon to display with the notifications of this notification
|
|
293
|
+
type. Defaults to `None`.
|
|
294
|
+
|
|
295
|
+
.. note:: In Growl 1.3.3, `icon` does not work.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
def __init__(self, name, display_name=None, enabled=True, icon=None):
|
|
299
|
+
self.name = name
|
|
300
|
+
self.display_name = display_name
|
|
301
|
+
self.enabled = enabled
|
|
302
|
+
self.icon = icon
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class Notification(object):
|
|
306
|
+
"""Represent notification."""
|
|
307
|
+
|
|
308
|
+
def __init__(self, name, title, text='', id_=None, sticky=None,
|
|
309
|
+
priority=None, icon=None, coalescing_id=None,
|
|
310
|
+
gntp_callback=None, **socket_callback_options):
|
|
311
|
+
self.name = name
|
|
312
|
+
self.title = title
|
|
313
|
+
self.text = text
|
|
314
|
+
self.id_ = id_
|
|
315
|
+
self.sticky = sticky
|
|
316
|
+
self.priority = priority
|
|
317
|
+
self.icon = icon
|
|
318
|
+
self.coalescing_id = coalescing_id
|
|
319
|
+
self.callback = coerce_to_callback(gntp_callback,
|
|
320
|
+
**socket_callback_options)
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def socket_callback(self):
|
|
324
|
+
if isinstance(self.callback, SocketCallback):
|
|
325
|
+
return self.callback
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class BaseRequest(object):
|
|
329
|
+
"""Abstract base class for GNTP request.
|
|
330
|
+
|
|
331
|
+
:param custom_headers: the list of key-value tuples for Custom Headers.
|
|
332
|
+
:param app_specific_headers: the list of key-value tuples for App-Specific
|
|
333
|
+
Headers.
|
|
334
|
+
"""
|
|
335
|
+
#: Request message type. Subclasses must override this attribute.
|
|
336
|
+
message_type = None
|
|
337
|
+
|
|
338
|
+
def __init__(self, custom_headers=None, app_specific_headers=None):
|
|
339
|
+
self.custom_headers = custom_headers or []
|
|
340
|
+
self.app_specific_headers = app_specific_headers or []
|
|
341
|
+
|
|
342
|
+
def write_into(self, writer):
|
|
343
|
+
"""Subclasses must call this method first to serialize their
|
|
344
|
+
message."""
|
|
345
|
+
writer.write_base_request(self)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class RegisterRequest(BaseRequest):
|
|
349
|
+
"""Represent ``REGISTER`` request.
|
|
350
|
+
|
|
351
|
+
:param app_name: the name of the application.
|
|
352
|
+
:param app_icon: url string or an instance of :class:`Resource` for the
|
|
353
|
+
icon of the application.
|
|
354
|
+
:param events: the list of :class:`Event` instances.
|
|
355
|
+
:param custom_headers: the list of key-value tuples for Custom Headers.
|
|
356
|
+
:param app_specific_headers: the list of key-value tuples for App-Specific
|
|
357
|
+
Headers.
|
|
358
|
+
|
|
359
|
+
.. note:: In Growl 1.3.3, `app_icon` of url string does not work.
|
|
360
|
+
"""
|
|
361
|
+
message_type = 'REGISTER'
|
|
362
|
+
|
|
363
|
+
def __init__(self, app_name, app_icon, events, custom_headers=None,
|
|
364
|
+
app_specific_headers=None):
|
|
365
|
+
BaseRequest.__init__(self, custom_headers, app_specific_headers)
|
|
366
|
+
self.app_name = app_name
|
|
367
|
+
self.app_icon = app_icon
|
|
368
|
+
self.events = events
|
|
369
|
+
|
|
370
|
+
def write_into(self, writer):
|
|
371
|
+
BaseRequest.write_into(self, writer)
|
|
372
|
+
writer.write_register_request(self)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class NotifyRequest(BaseRequest):
|
|
376
|
+
"""Represent ``NOTIFY`` request.
|
|
377
|
+
|
|
378
|
+
:param app_name: the name of the application.
|
|
379
|
+
:param notification: :class:`Notification` instance.
|
|
380
|
+
:param custom_headers: the list of key-value tuples for Custom Headers.
|
|
381
|
+
:param app_specific_headers: the list of key-value tuples for App-Specific
|
|
382
|
+
Headers.
|
|
383
|
+
"""
|
|
384
|
+
message_type = 'NOTIFY'
|
|
385
|
+
|
|
386
|
+
def __init__(self, app_name, notification, custom_headers=None,
|
|
387
|
+
app_specific_headers=None):
|
|
388
|
+
BaseRequest.__init__(self, custom_headers, app_specific_headers)
|
|
389
|
+
self.app_name = app_name
|
|
390
|
+
self.notification = notification
|
|
391
|
+
|
|
392
|
+
def write_into(self, writer):
|
|
393
|
+
BaseRequest.write_into(self, writer)
|
|
394
|
+
writer.write_notify_request(self)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class SubscribeRequest(BaseRequest):
|
|
398
|
+
"""Represent ``SUBSCRIBE`` request.
|
|
399
|
+
|
|
400
|
+
:param id_: the unique id of the subscriber.
|
|
401
|
+
:param name: the name of the subscriber.
|
|
402
|
+
:param port: the port number of the subscriber.
|
|
403
|
+
:param custom_headers: the list of key-value tuples for Custom Headers.
|
|
404
|
+
:param app_specific_headers: the list of key-value tuples for App-Specific
|
|
405
|
+
Headers.
|
|
406
|
+
"""
|
|
407
|
+
message_type = 'SUBSCRIBE'
|
|
408
|
+
|
|
409
|
+
def __init__(self, id_, name, port, custom_headers=None,
|
|
410
|
+
app_specific_headers=None):
|
|
411
|
+
BaseRequest.__init__(self, custom_headers, app_specific_headers)
|
|
412
|
+
self.id_ = id_
|
|
413
|
+
self.name = name
|
|
414
|
+
self.port = port
|
|
415
|
+
|
|
416
|
+
def write_into(self, writer):
|
|
417
|
+
BaseRequest.write_into(self, writer)
|
|
418
|
+
writer.write_subscribe_request(self)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class Response(object):
|
|
422
|
+
"""Base class for GNTP response.
|
|
423
|
+
|
|
424
|
+
:param message_type: <messagetype> of the response. `'-OK'`, `'-ERROR'` or
|
|
425
|
+
`'-CALLBACK'`.
|
|
426
|
+
:param headers: headers of the response.
|
|
427
|
+
"""
|
|
428
|
+
|
|
429
|
+
def __init__(self, message_type, headers):
|
|
430
|
+
self.message_type = message_type
|
|
431
|
+
self.headers = headers
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
class BaseGNTPConnection(object):
|
|
435
|
+
"""Abstract base class for GNTP connection."""
|
|
436
|
+
|
|
437
|
+
def __init__(self, final_callback, socket_callback=None):
|
|
438
|
+
self.final_callback = final_callback
|
|
439
|
+
self.socket_callback = socket_callback
|
|
440
|
+
|
|
441
|
+
def on_ok_message(self, message):
|
|
442
|
+
r"""Callback for ``-OK`` response.
|
|
443
|
+
|
|
444
|
+
:param message: string of response terminated by `'\\r\\n\\r\\n'`.
|
|
445
|
+
"""
|
|
446
|
+
try:
|
|
447
|
+
response = parse_response(message, '-OK')
|
|
448
|
+
if self.socket_callback is not None:
|
|
449
|
+
self.read_message(self.on_callback_message)
|
|
450
|
+
finally:
|
|
451
|
+
if self.socket_callback is None:
|
|
452
|
+
self.close()
|
|
453
|
+
if self.socket_callback is None and self.final_callback is not None:
|
|
454
|
+
self.final_callback(response)
|
|
455
|
+
|
|
456
|
+
def on_callback_message(self, message):
|
|
457
|
+
r"""Callback for ``-CALLBACK`` response.
|
|
458
|
+
|
|
459
|
+
:param message: string of response terminated by `'\\r\\n\\r\\n'`.
|
|
460
|
+
"""
|
|
461
|
+
try:
|
|
462
|
+
response = parse_response(message, '-CALLBACK')
|
|
463
|
+
callback_result = self.socket_callback(response)
|
|
464
|
+
finally:
|
|
465
|
+
self.close()
|
|
466
|
+
if self.final_callback is not None:
|
|
467
|
+
self.final_callback(callback_result)
|
|
468
|
+
|
|
469
|
+
def write_message(self, message):
|
|
470
|
+
"""Subclasses must override this method to send a message to the GNTP
|
|
471
|
+
server."""
|
|
472
|
+
raise NotImplementedError
|
|
473
|
+
|
|
474
|
+
def read_message(self, callback):
|
|
475
|
+
"""Subclasses must override this method to receive a message from the
|
|
476
|
+
GNTP server."""
|
|
477
|
+
raise NotImplementedError
|
|
478
|
+
|
|
479
|
+
def close(self):
|
|
480
|
+
"""Subclasses must override this method to close the connection with
|
|
481
|
+
the GNTP server."""
|
|
482
|
+
raise NotImplementedError
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class GNTPConnection(BaseGNTPConnection):
|
|
486
|
+
"""Represent the connection with the GNTP server."""
|
|
487
|
+
|
|
488
|
+
def __init__(self, address, timeout, final_callback, socket_callback=None):
|
|
489
|
+
BaseGNTPConnection.__init__(self, final_callback, socket_callback)
|
|
490
|
+
self.sock = socket.create_connection(address, timeout=timeout)
|
|
491
|
+
|
|
492
|
+
def write_message(self, message):
|
|
493
|
+
"""Send the request message to the GNTP server."""
|
|
494
|
+
self.sock.send(message)
|
|
495
|
+
|
|
496
|
+
def read_message(self, callback):
|
|
497
|
+
"""Read a message from opened socket and run callback with it."""
|
|
498
|
+
message = next(generate_messages(self.sock))
|
|
499
|
+
callback(message)
|
|
500
|
+
|
|
501
|
+
def close(self):
|
|
502
|
+
"""Close the socket."""
|
|
503
|
+
self.sock.close()
|
|
504
|
+
self.sock = None
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class GNTPClient(object):
|
|
508
|
+
"""GNTP client.
|
|
509
|
+
|
|
510
|
+
:param host: host of GNTP server. Defaults to `'localhost'`.
|
|
511
|
+
:param port: port of GNTP server. Defaults to `23053`.
|
|
512
|
+
:param timeout: timeout in seconds. Defaults to `10`.
|
|
513
|
+
:param password: the password used in creating the key.
|
|
514
|
+
:param key_hashing: the type of hash algorithm used in creating the key.
|
|
515
|
+
It is `keys.MD5`, `keys.SHA1`, `keys.SHA256` or
|
|
516
|
+
`keys.SHA512`. Defaults to `keys.SHA256`.
|
|
517
|
+
:param encryption: the tyep of encryption algorithm used.
|
|
518
|
+
It is `None`, `ciphers.AES`, `ciphers.DES` or
|
|
519
|
+
`ciphers.3DES`. `None` means no encryption.
|
|
520
|
+
Defaults to `None`.
|
|
521
|
+
:param connection_class: GNTP connection class. If it is `None`,
|
|
522
|
+
:class:`GNTPConnection` is used. Defaults to
|
|
523
|
+
`None`.
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
def __init__(self, host='localhost', port=DEFAULT_PORT,
|
|
527
|
+
timeout=DEFAULT_TIMEOUT, password=None,
|
|
528
|
+
key_hashing=keys.SHA256, encryption=None,
|
|
529
|
+
connection_class=None):
|
|
530
|
+
self.address = (host, port)
|
|
531
|
+
self.timeout = timeout
|
|
532
|
+
self.connection_class = connection_class or GNTPConnection
|
|
533
|
+
if (encryption is not None and
|
|
534
|
+
encryption.key_size > key_hashing.key_size):
|
|
535
|
+
raise GNTPError('key_hashing key size (%s:%d) must be at'
|
|
536
|
+
' least encryption key size (%s:%d)' % (
|
|
537
|
+
key_hashing.algorithm_id, key_hashing.key_size,
|
|
538
|
+
encryption.algorithm_id, encryption.key_size))
|
|
539
|
+
self.packer_factory = MessagePackerFactory(password, key_hashing,
|
|
540
|
+
encryption)
|
|
541
|
+
|
|
542
|
+
def process_request(self, request, callback, **kwargs):
|
|
543
|
+
"""Process a request.
|
|
544
|
+
|
|
545
|
+
:param callback: the final callback run after closing connection.
|
|
546
|
+
"""
|
|
547
|
+
packer = self.packer_factory.create()
|
|
548
|
+
message = packer.pack(request)
|
|
549
|
+
conn = self._connect(callback, **kwargs)
|
|
550
|
+
conn.write_message(message)
|
|
551
|
+
conn.read_message(conn.on_ok_message)
|
|
552
|
+
|
|
553
|
+
def _connect(self, final_callback, **kwargs):
|
|
554
|
+
"""Connect to the GNTP server and return the connection."""
|
|
555
|
+
return self.connection_class(self.address, self.timeout,
|
|
556
|
+
final_callback, **kwargs)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def generate_messages(sock, size=1024):
|
|
560
|
+
"""Generate messages from opened socket."""
|
|
561
|
+
buf = b''
|
|
562
|
+
while True:
|
|
563
|
+
buf += sock.recv(size)
|
|
564
|
+
if not buf:
|
|
565
|
+
break
|
|
566
|
+
pos = buf.find(MESSAGE_DELIMITER)
|
|
567
|
+
if ((pos < 0 and len(buf) >= MAX_MESSAGE_SIZE) or
|
|
568
|
+
(pos > MAX_MESSAGE_SIZE - MESSAGE_DELIMITER_SIZE)):
|
|
569
|
+
raise GNTPError('too large message: %r' % buf)
|
|
570
|
+
elif pos > 0:
|
|
571
|
+
pos += 4
|
|
572
|
+
yield buf[:pos]
|
|
573
|
+
buf = buf[pos:]
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def parse_response(message, expected_message_type=None):
|
|
577
|
+
"""Parse response and return response object."""
|
|
578
|
+
try:
|
|
579
|
+
lines = [line for line in message.split(LINE_DELIMITER) if line]
|
|
580
|
+
_, message_type = parse_information_line(lines.pop(0))
|
|
581
|
+
if (expected_message_type is not None and
|
|
582
|
+
expected_message_type != message_type):
|
|
583
|
+
raise GNTPError('%s is not expected message type %s' % (
|
|
584
|
+
message_type, expected_message_type))
|
|
585
|
+
|
|
586
|
+
headers = dict([s.strip().decode('utf-8') for s in line.split(b':', 1)]
|
|
587
|
+
for line in lines)
|
|
588
|
+
if message_type == '-ERROR':
|
|
589
|
+
raise GNTPError('%s: %s' % (headers['Error-Code'],
|
|
590
|
+
headers['Error-Description']))
|
|
591
|
+
return Response(message_type, headers)
|
|
592
|
+
except ValueError as exc:
|
|
593
|
+
raise GNTPError(exc.args[0], 'original message: %r' % message)
|
|
594
|
+
except GNTPError as exc:
|
|
595
|
+
exc.args = (exc.args[0], 'original message: %r' % message)
|
|
596
|
+
raise exc
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def parse_information_line(line):
|
|
600
|
+
"""Parse information line and return tuple (`<version>`,
|
|
601
|
+
`<messagetype>`)."""
|
|
602
|
+
matched = RESPONSE_INFORMATION_LINE_RE.match(line)
|
|
603
|
+
if matched is None:
|
|
604
|
+
raise GNTPError('invalid information line: %r' % line)
|
|
605
|
+
version, message_type = [s.decode('utf-8') for s in matched.groups()]
|
|
606
|
+
if version not in SUPPORTED_VERSIONS:
|
|
607
|
+
raise GNTPError("version '%s' is not supported" % version)
|
|
608
|
+
return version, message_type
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def coerce_to_events(items):
|
|
612
|
+
"""Coerce the list of the event definitions to the list of :class:`Event`
|
|
613
|
+
instances."""
|
|
614
|
+
results = []
|
|
615
|
+
for item in items:
|
|
616
|
+
if isinstance(item, (bytes, text_type)):
|
|
617
|
+
results.append(Event(item, enabled=True))
|
|
618
|
+
elif isinstance(item, tuple):
|
|
619
|
+
name, enabled = item
|
|
620
|
+
results.append(Event(name, enabled=enabled))
|
|
621
|
+
elif isinstance(item, Event):
|
|
622
|
+
results.append(item)
|
|
623
|
+
return results
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
class Resource(object):
|
|
627
|
+
"""Class for <uniqueid> data types.
|
|
628
|
+
|
|
629
|
+
:param data: the binary content.
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
def __init__(self, data):
|
|
633
|
+
self.data = data
|
|
634
|
+
self._unique_value = None
|
|
635
|
+
|
|
636
|
+
def unique_value(self):
|
|
637
|
+
"""Return the <uniquevalue> value."""
|
|
638
|
+
if self.data is not None and self._unique_value is None:
|
|
639
|
+
try:
|
|
640
|
+
self._unique_value = hashlib.md5(self.data).hexdigest().encode('utf-8')
|
|
641
|
+
except:
|
|
642
|
+
self._unique_value = hashlib.md5(self.data.encode('utf-8')).hexdigest().encode('utf-8')
|
|
643
|
+
return self._unique_value
|
|
644
|
+
|
|
645
|
+
def unique_id(self):
|
|
646
|
+
"""Return the <uniqueid> value."""
|
|
647
|
+
if self.data is not None:
|
|
648
|
+
return b'x-growl-resource://' + self.unique_value()
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
class RawIcon(Resource):
|
|
652
|
+
"""Deprecated icon class."""
|
|
653
|
+
|
|
654
|
+
def __init__(self, data):
|
|
655
|
+
import warnings
|
|
656
|
+
warnings.warn('RawIcon is deprecated, use Resource instead',
|
|
657
|
+
DeprecationWarning, stacklevel=2)
|
|
658
|
+
Resource.__init__(self, data)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
class SocketCallback(object):
|
|
662
|
+
"""Base class for socket callback.
|
|
663
|
+
|
|
664
|
+
Each of the callbacks takes one positional argument, which is
|
|
665
|
+
:class:`Response` instance.
|
|
666
|
+
|
|
667
|
+
:param context: value of ``Notification-Callback-Context``.
|
|
668
|
+
Defaults to ``'None'``.
|
|
669
|
+
:param context-type: value of ``Notification-Callback-Context-Type``.
|
|
670
|
+
Defaults to ``'None'``.
|
|
671
|
+
:param on_click: the callback run at ``CLICKED`` callback result.
|
|
672
|
+
:param on_close: the callback run at ``CLOSED`` callback result.
|
|
673
|
+
:param on_timeout: the callback run at ``TIMEDOUT`` callback result.
|
|
674
|
+
|
|
675
|
+
.. note:: TIMEDOUT callback does not occur in my Growl 1.3.3.
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
def __init__(self, context='None', context_type='None',
|
|
679
|
+
on_click=None, on_close=None, on_timeout=None):
|
|
680
|
+
self.context = context
|
|
681
|
+
self.context_type = context_type
|
|
682
|
+
self.on_click_callback = on_click
|
|
683
|
+
self.on_close_callback = on_close
|
|
684
|
+
self.on_timeout_callback = on_timeout
|
|
685
|
+
|
|
686
|
+
def on_click(self, response):
|
|
687
|
+
"""Run ``CLICKED`` event callback."""
|
|
688
|
+
if self.on_click_callback is not None:
|
|
689
|
+
return self.on_click_callback(response)
|
|
690
|
+
|
|
691
|
+
def on_close(self, response):
|
|
692
|
+
"""Run ``CLOSED`` event callback."""
|
|
693
|
+
if self.on_close_callback is not None:
|
|
694
|
+
return self.on_close_callback(response)
|
|
695
|
+
|
|
696
|
+
def on_timeout(self, response):
|
|
697
|
+
"""Run ``TIMEDOUT`` event callback."""
|
|
698
|
+
if self.on_timeout_callback is not None:
|
|
699
|
+
return self.on_timeout_callback(response)
|
|
700
|
+
|
|
701
|
+
def __call__(self, response):
|
|
702
|
+
"""This is the callback. Delegate to ``on_`` methods depending on
|
|
703
|
+
``Notification-Callback-Result`` value.
|
|
704
|
+
|
|
705
|
+
:param response: :class:`Response` instance.
|
|
706
|
+
"""
|
|
707
|
+
callback_result = response.headers['Notification-Callback-Result']
|
|
708
|
+
delegate_map = {
|
|
709
|
+
'CLICKED': self.on_click, 'CLICK': self.on_click,
|
|
710
|
+
'CLOSED': self.on_close, 'CLOSE': self.on_close,
|
|
711
|
+
'TIMEDOUT': self.on_timeout, 'TIMEOUT': self.on_timeout,
|
|
712
|
+
}
|
|
713
|
+
return delegate_map[callback_result](response)
|
|
714
|
+
|
|
715
|
+
def write_into(self, writer):
|
|
716
|
+
writer.write_socket_callback(self)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
class URLCallback(object):
|
|
720
|
+
"""Class for url callback."""
|
|
721
|
+
|
|
722
|
+
def __init__(self, url):
|
|
723
|
+
self.url = url
|
|
724
|
+
|
|
725
|
+
def write_into(self, writer):
|
|
726
|
+
writer.write_url_callback(self)
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def coerce_to_callback(gntp_callback=None, **socket_callback_options):
|
|
730
|
+
"""Return :class:`URLCallback` instance for url callback or
|
|
731
|
+
:class:`SocketCallback` instance for socket callback.
|
|
732
|
+
|
|
733
|
+
If `gntp_callback` is not `None`, `socket_callback_options` must be empty.
|
|
734
|
+
Moreover, if `gntp_callback` is string, then a instance of
|
|
735
|
+
:class:`URLCallback` is returned. Otherwise, `gntp_callback` is returned
|
|
736
|
+
directly.
|
|
737
|
+
|
|
738
|
+
If `gntp_callback` is `None` and `socket_callback_options` is not empty,
|
|
739
|
+
new instance of :class:`SocketCallback` is created from given keyword
|
|
740
|
+
arguments and it is returned. Acceptable keyword arguments are same as
|
|
741
|
+
constructor's of :class:`SocketCallback`.
|
|
742
|
+
"""
|
|
743
|
+
if gntp_callback is not None:
|
|
744
|
+
if socket_callback_options:
|
|
745
|
+
raise GNTPError('If gntp_callback is not None,'
|
|
746
|
+
' socket_callback_options must be empty')
|
|
747
|
+
if isinstance(gntp_callback, (bytes, text_type)):
|
|
748
|
+
return URLCallback(gntp_callback)
|
|
749
|
+
else:
|
|
750
|
+
return gntp_callback
|
|
751
|
+
if socket_callback_options:
|
|
752
|
+
return SocketCallback(**socket_callback_options)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
class _NullCipher(object):
|
|
756
|
+
"""Null object for the encryption of messages."""
|
|
757
|
+
|
|
758
|
+
algorithm = None
|
|
759
|
+
algorithm_id = 'NONE'
|
|
760
|
+
encrypt = lambda self, text: text
|
|
761
|
+
decrypt = lambda self, text: text
|
|
762
|
+
__bool__ = lambda self: False
|
|
763
|
+
__nonzero__ = __bool__
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
NullCipher = _NullCipher()
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
class MessagePackerFactory(object):
|
|
770
|
+
"""The factory of :class:`MessagePacker`.
|
|
771
|
+
|
|
772
|
+
If `password` is None, `hashing` and `encryption` are ignored.
|
|
773
|
+
"""
|
|
774
|
+
|
|
775
|
+
def __init__(self, password=None, hashing=keys.SHA256, encryption=None):
|
|
776
|
+
self.password = password
|
|
777
|
+
self.hashing = password and hashing
|
|
778
|
+
self.encryption = (password and encryption) or NullCipher
|
|
779
|
+
|
|
780
|
+
def create(self):
|
|
781
|
+
"""Create an instance of :class:`MessagePacker` and return it."""
|
|
782
|
+
key = self.password and self.hashing.key(self.password)
|
|
783
|
+
cipher = self.encryption and self.encryption.cipher(key)
|
|
784
|
+
return MessagePacker(key, cipher)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
class MessagePacker(object):
|
|
788
|
+
"""The serializer for messages.
|
|
789
|
+
|
|
790
|
+
`key` and `cipher` have random-generated salt and iv respectively.
|
|
791
|
+
|
|
792
|
+
:param key: an instance of :class:`keys.Key`.
|
|
793
|
+
:param cipher: an instance of :class:`ciphers.Cipher` or `NullCipher`.
|
|
794
|
+
"""
|
|
795
|
+
|
|
796
|
+
def __init__(self, key=None, cipher=None):
|
|
797
|
+
self.key = key
|
|
798
|
+
self.cipher = cipher or NullCipher
|
|
799
|
+
|
|
800
|
+
def pack(self, request):
|
|
801
|
+
"""Return utf-8 encoded request message."""
|
|
802
|
+
return (InformationLinePacker(self.key, self.cipher).pack(request) +
|
|
803
|
+
LINE_DELIMITER +
|
|
804
|
+
HeaderPacker(self.cipher).pack(request) +
|
|
805
|
+
SectionPacker(self.cipher).pack(request) +
|
|
806
|
+
LINE_DELIMITER)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
class InformationLinePacker(object):
|
|
810
|
+
|
|
811
|
+
def __init__(self, key, cipher):
|
|
812
|
+
self.key = key
|
|
813
|
+
self.cipher = cipher
|
|
814
|
+
|
|
815
|
+
def pack(self, request):
|
|
816
|
+
"""Return utf-8 encoded information line."""
|
|
817
|
+
result = (b'GNTP/1.0 ' +
|
|
818
|
+
request.message_type.encode('utf-8') +
|
|
819
|
+
b' ' +
|
|
820
|
+
self.cipher.algorithm_id.encode('utf-8'))
|
|
821
|
+
if self.cipher.algorithm is not None:
|
|
822
|
+
result += b':' + self.cipher.iv_hex
|
|
823
|
+
if self.key is not None:
|
|
824
|
+
result += (b' ' +
|
|
825
|
+
self.key.algorithm_id.encode('utf-8') +
|
|
826
|
+
b':' +
|
|
827
|
+
self.key.key_hash_hex +
|
|
828
|
+
b'.' +
|
|
829
|
+
self.key.salt_hex)
|
|
830
|
+
return result
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
class HeaderPacker(object):
|
|
834
|
+
|
|
835
|
+
def __init__(self, cipher):
|
|
836
|
+
self.writer = io.BytesIO()
|
|
837
|
+
self.cipher = cipher
|
|
838
|
+
|
|
839
|
+
def pack(self, request):
|
|
840
|
+
"""Return utf-8 encoded headers."""
|
|
841
|
+
request.write_into(self)
|
|
842
|
+
headers = self.writer.getvalue()
|
|
843
|
+
result = self.cipher.encrypt(headers)
|
|
844
|
+
if self.cipher.algorithm is not None:
|
|
845
|
+
result += LINE_DELIMITER
|
|
846
|
+
return result
|
|
847
|
+
|
|
848
|
+
def write_base_request(self, request):
|
|
849
|
+
self._write_additional_headers(request.custom_headers,
|
|
850
|
+
CUSTOM_HEADER_PREFIX)
|
|
851
|
+
self._write_additional_headers(request.app_specific_headers,
|
|
852
|
+
APP_SPECIFIC_HEADER_PREFIX)
|
|
853
|
+
|
|
854
|
+
def _write_additional_headers(self, headers, prefix):
|
|
855
|
+
for key, value in headers:
|
|
856
|
+
if not key.startswith(prefix):
|
|
857
|
+
key = prefix + key
|
|
858
|
+
self.write(key.encode('utf-8'), value)
|
|
859
|
+
|
|
860
|
+
def write_register_request(self, request):
|
|
861
|
+
self.write(b'Application-Name', request.app_name)
|
|
862
|
+
self.write(b'Application-Icon', request.app_icon)
|
|
863
|
+
self.write(b'Notifications-Count', len(request.events))
|
|
864
|
+
for event in request.events:
|
|
865
|
+
self.writer.write(LINE_DELIMITER)
|
|
866
|
+
self.write(b'Notification-Name', event.name)
|
|
867
|
+
self.write(b'Notification-Display-Name', event.display_name)
|
|
868
|
+
self.write(b'Notification-Enabled', event.enabled)
|
|
869
|
+
self.write(b'Notification-Icon', event.icon)
|
|
870
|
+
|
|
871
|
+
def write_notify_request(self, request):
|
|
872
|
+
self.write(b'Application-Name', request.app_name)
|
|
873
|
+
self._write_notification(request.notification)
|
|
874
|
+
|
|
875
|
+
def write_subscribe_request(self, request):
|
|
876
|
+
self.write(b'Subscriber-ID', request.id_)
|
|
877
|
+
self.write(b'Subscriber-Name', request.name)
|
|
878
|
+
self.write(b'Subscriber-Port', request.port)
|
|
879
|
+
|
|
880
|
+
def _write_notification(self, notification):
|
|
881
|
+
self.write(b'Notification-Name', notification.name)
|
|
882
|
+
self.write(b'Notification-ID', notification.id_)
|
|
883
|
+
self.write(b'Notification-Title', notification.title)
|
|
884
|
+
self.write(b'Notification-Text', notification.text)
|
|
885
|
+
self.write(b'Notification-Sticky', notification.sticky)
|
|
886
|
+
self.write(b'Notification-Priority', notification.priority)
|
|
887
|
+
self.write(b'Notification-Icon', notification.icon)
|
|
888
|
+
self.write(b'Notification-Coalescing-ID', notification.coalescing_id)
|
|
889
|
+
if notification.callback is not None:
|
|
890
|
+
notification.callback.write_into(self)
|
|
891
|
+
|
|
892
|
+
def write_socket_callback(self, callback):
|
|
893
|
+
self.write(b'Notification-Callback-Context', callback.context)
|
|
894
|
+
self.write(b'Notification-Callback-Context-Type',
|
|
895
|
+
callback.context_type)
|
|
896
|
+
|
|
897
|
+
def write_url_callback(self, callback):
|
|
898
|
+
self.write(b'Notification-Callback-Target', callback.url)
|
|
899
|
+
|
|
900
|
+
def write(self, name, value):
|
|
901
|
+
"""Write utf-8 encoded header into writer.
|
|
902
|
+
|
|
903
|
+
:param name: the name of the header.
|
|
904
|
+
:param value: the value of the header.
|
|
905
|
+
"""
|
|
906
|
+
if isinstance(value, Resource):
|
|
907
|
+
value = value.unique_id()
|
|
908
|
+
if value is not None:
|
|
909
|
+
if not isinstance(value, bytes):
|
|
910
|
+
value = text_type(value).encode('utf-8')
|
|
911
|
+
self.writer.write(name)
|
|
912
|
+
self.writer.write(b': ')
|
|
913
|
+
self.writer.write(value)
|
|
914
|
+
self.writer.write(LINE_DELIMITER)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
class SectionPacker(object):
|
|
918
|
+
|
|
919
|
+
def __init__(self, cipher):
|
|
920
|
+
self.writer = io.BytesIO()
|
|
921
|
+
self.cipher = cipher
|
|
922
|
+
|
|
923
|
+
def pack(self, request):
|
|
924
|
+
"""Return utf-8 encoded message body."""
|
|
925
|
+
request.write_into(self)
|
|
926
|
+
return self.writer.getvalue()
|
|
927
|
+
|
|
928
|
+
def write_base_request(self, request):
|
|
929
|
+
for _, value in request.custom_headers:
|
|
930
|
+
self.write(value)
|
|
931
|
+
for _, value in request.app_specific_headers:
|
|
932
|
+
self.write(value)
|
|
933
|
+
|
|
934
|
+
def write_register_request(self, request):
|
|
935
|
+
self.write(request.app_icon)
|
|
936
|
+
for event in request.events:
|
|
937
|
+
self.write(event.icon)
|
|
938
|
+
|
|
939
|
+
def write_notify_request(self, request):
|
|
940
|
+
self.write(request.notification.icon)
|
|
941
|
+
|
|
942
|
+
def write_subscribe_request(self, request):
|
|
943
|
+
pass
|
|
944
|
+
|
|
945
|
+
def write(self, resource):
|
|
946
|
+
"""Write utf-8 encoded resource into writer.
|
|
947
|
+
|
|
948
|
+
:param headers: the iterable of (`name`, `value`) tuple of the header.
|
|
949
|
+
:param body: bytes of section body.
|
|
950
|
+
"""
|
|
951
|
+
if isinstance(resource, Resource) and resource.data is not None:
|
|
952
|
+
data = self.cipher.encrypt(resource.data)
|
|
953
|
+
self.writer.write(SECTION_DELIMITER)
|
|
954
|
+
self.writer.write(b'Identifier: ')
|
|
955
|
+
self.writer.write(resource.unique_value())
|
|
956
|
+
self.writer.write(LINE_DELIMITER)
|
|
957
|
+
self.writer.write(b'Length: ')
|
|
958
|
+
self.writer.write(text_type(len(data)).encode('utf-8'))
|
|
959
|
+
self.writer.write(LINE_DELIMITER)
|
|
960
|
+
self.writer.write(SECTION_BODY_START)
|
|
961
|
+
if sys.version_info.major < 3:
|
|
962
|
+
self.writer.write(data)
|
|
963
|
+
else:
|
|
964
|
+
# print("DATA =", data)
|
|
965
|
+
# print("type(DATA) =", type(data))
|
|
966
|
+
if sys.version_info.major == 2:
|
|
967
|
+
self.writer.write(data)
|
|
968
|
+
else:
|
|
969
|
+
if isinstance(data, bytes):
|
|
970
|
+
self.writer.write(data)
|
|
971
|
+
else:
|
|
972
|
+
self.writer.write(bytes(data, encoding='utf-8'))
|
|
973
|
+
self.writer.write(SECTION_BODY_END)
|