Browse Source

Implement pooled connections

pull/86/head
Toromino 1 month ago
parent
commit
5dd167e427

+ 121
- 85
Cargo.lock View File

@@ -15,7 +15,7 @@ dependencies = [
15 15
 
16 16
 [[package]]
17 17
 name = "arrayvec"
18
-version = "0.4.10"
18
+version = "0.4.11"
19 19
 source = "registry+https://github.com/rust-lang/crates.io-index"
20 20
 dependencies = [
21 21
  "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -23,11 +23,10 @@ dependencies = [
23 23
 
24 24
 [[package]]
25 25
 name = "atty"
26
-version = "0.2.11"
26
+version = "0.2.12"
27 27
 source = "registry+https://github.com/rust-lang/crates.io-index"
28 28
 dependencies = [
29 29
  "libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
30
- "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
31 30
  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
32 31
 ]
33 32
 
@@ -157,14 +156,6 @@ dependencies = [
157 156
 ]
158 157
 
159 158
 [[package]]
160
-name = "cached"
161
-version = "0.9.0"
162
-source = "registry+https://github.com/rust-lang/crates.io-index"
163
-dependencies = [
164
- "once_cell 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
165
-]
166
-
167
-[[package]]
168 159
 name = "cc"
169 160
 version = "1.0.37"
170 161
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -236,7 +227,7 @@ dependencies = [
236 227
  "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
237 228
  "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
238 229
  "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
239
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
230
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
240 231
  "publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
241 232
  "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
242 233
  "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -281,7 +272,7 @@ name = "crossbeam-epoch"
281 272
 version = "0.7.1"
282 273
 source = "registry+https://github.com/rust-lang/crates.io-index"
283 274
 dependencies = [
284
- "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
275
+ "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
285 276
  "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
286 277
  "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
287 278
  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -326,7 +317,7 @@ version = "0.2.0"
326 317
 source = "registry+https://github.com/rust-lang/crates.io-index"
327 318
 dependencies = [
328 319
  "devise_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
329
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
320
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
330 321
 ]
331 322
 
332 323
 [[package]]
@@ -336,7 +327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
336 327
 dependencies = [
337 328
  "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
338 329
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
339
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
330
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
340 331
  "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
341 332
 ]
342 333
 
@@ -350,6 +341,7 @@ dependencies = [
350 341
  "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
351 342
  "diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
352 343
  "pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
344
+ "r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
353 345
  "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
354 346
 ]
355 347
 
@@ -359,7 +351,7 @@ version = "1.4.0"
359 351
 source = "registry+https://github.com/rust-lang/crates.io-index"
360 352
 dependencies = [
361 353
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
362
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
354
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
363 355
  "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
364 356
 ]
365 357
 
@@ -413,7 +405,7 @@ version = "0.1.5"
413 405
 source = "registry+https://github.com/rust-lang/crates.io-index"
414 406
 dependencies = [
415 407
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
416
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
408
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
417 409
  "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
418 410
  "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
419 411
 ]
@@ -557,13 +549,22 @@ dependencies = [
557 549
  "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
558 550
  "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
559 551
  "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
560
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
552
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
561 553
  "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
562 554
  "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
563 555
  "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
564 556
 ]
565 557
 
566 558
 [[package]]
559
+name = "hashbrown"
560
+version = "0.1.8"
561
+source = "registry+https://github.com/rust-lang/crates.io-index"
562
+dependencies = [
563
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
564
+ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
565
+]
566
+
567
+[[package]]
567 568
 name = "http"
568 569
 version = "0.1.17"
569 570
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -614,7 +615,7 @@ dependencies = [
614 615
 
615 616
 [[package]]
616 617
 name = "hyper"
617
-version = "0.12.31"
618
+version = "0.12.32"
618 619
 source = "registry+https://github.com/rust-lang/crates.io-index"
619 620
 dependencies = [
620 621
  "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -626,7 +627,7 @@ dependencies = [
626 627
  "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
627 628
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
628 629
  "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
629
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
630
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
630 631
  "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
631 632
  "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
632 633
  "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -648,7 +649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
648 649
 dependencies = [
649 650
  "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
650 651
  "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
651
- "hyper 0.12.31 (registry+https://github.com/rust-lang/crates.io-index)",
652
+ "hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)",
652 653
  "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
653 654
  "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
654 655
 ]
@@ -715,12 +716,12 @@ version = "0.1.0"
715 716
 dependencies = [
716 717
  "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
717 718
  "bcrypt 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
718
- "cached 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
719 719
  "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
720 720
  "config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
721 721
  "diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
722 722
  "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
723 723
  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
724
+ "lru 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
724 725
  "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)",
725 726
  "openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)",
726 727
  "pem 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -786,22 +787,38 @@ dependencies = [
786 787
 ]
787 788
 
788 789
 [[package]]
790
+name = "lock_api"
791
+version = "0.2.0"
792
+source = "registry+https://github.com/rust-lang/crates.io-index"
793
+dependencies = [
794
+ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
795
+]
796
+
797
+[[package]]
789 798
 name = "log"
790 799
 version = "0.3.9"
791 800
 source = "registry+https://github.com/rust-lang/crates.io-index"
792 801
 dependencies = [
793
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
802
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
794 803
 ]
795 804
 
796 805
 [[package]]
797 806
 name = "log"
798
-version = "0.4.6"
807
+version = "0.4.7"
799 808
 source = "registry+https://github.com/rust-lang/crates.io-index"
800 809
 dependencies = [
801 810
  "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
802 811
 ]
803 812
 
804 813
 [[package]]
814
+name = "lru"
815
+version = "0.1.15"
816
+source = "registry+https://github.com/rust-lang/crates.io-index"
817
+dependencies = [
818
+ "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
819
+]
820
+
821
+[[package]]
805 822
 name = "maplit"
806 823
 version = "1.0.1"
807 824
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -877,7 +894,7 @@ dependencies = [
877 894
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
878 895
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
879 896
  "libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
880
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
897
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
881 898
  "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
882 899
  "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
883 900
  "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -890,7 +907,7 @@ version = "2.0.5"
890 907
 source = "registry+https://github.com/rust-lang/crates.io-index"
891 908
 dependencies = [
892 909
  "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
893
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
910
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
894 911
  "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
895 912
  "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
896 913
 ]
@@ -913,7 +930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
913 930
 dependencies = [
914 931
  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
915 932
  "libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
916
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
933
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
917 934
  "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)",
918 935
  "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
919 936
  "openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -999,19 +1016,6 @@ dependencies = [
999 1016
 ]
1000 1017
 
1001 1018
 [[package]]
1002
-name = "numtoa"
1003
-version = "0.1.0"
1004
-source = "registry+https://github.com/rust-lang/crates.io-index"
1005
-
1006
-[[package]]
1007
-name = "once_cell"
1008
-version = "0.1.8"
1009
-source = "registry+https://github.com/rust-lang/crates.io-index"
1010
-dependencies = [
1011
- "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
1012
-]
1013
-
1014
-[[package]]
1015 1019
 name = "opaque-debug"
1016 1020
 version = "0.2.2"
1017 1021
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1064,6 +1068,16 @@ dependencies = [
1064 1068
 ]
1065 1069
 
1066 1070
 [[package]]
1071
+name = "parking_lot"
1072
+version = "0.8.0"
1073
+source = "registry+https://github.com/rust-lang/crates.io-index"
1074
+dependencies = [
1075
+ "lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
1076
+ "parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
1077
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
1078
+]
1079
+
1080
+[[package]]
1067 1081
 name = "parking_lot_core"
1068 1082
 version = "0.4.0"
1069 1083
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1076,6 +1090,21 @@ dependencies = [
1076 1090
 ]
1077 1091
 
1078 1092
 [[package]]
1093
+name = "parking_lot_core"
1094
+version = "0.5.0"
1095
+source = "registry+https://github.com/rust-lang/crates.io-index"
1096
+dependencies = [
1097
+ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
1098
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
1099
+ "libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
1100
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
1101
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
1102
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
1103
+ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
1104
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
1105
+]
1106
+
1107
+[[package]]
1079 1108
 name = "pear"
1080 1109
 version = "0.1.2"
1081 1110
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1089,7 +1118,7 @@ version = "0.1.2"
1089 1118
 source = "registry+https://github.com/rust-lang/crates.io-index"
1090 1119
 dependencies = [
1091 1120
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
1092
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
1121
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
1093 1122
  "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
1094 1123
  "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
1095 1124
  "yansi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1136,7 +1165,7 @@ dependencies = [
1136 1165
  "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
1137 1166
  "pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
1138 1167
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
1139
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
1168
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
1140 1169
  "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
1141 1170
 ]
1142 1171
 
@@ -1225,13 +1254,23 @@ dependencies = [
1225 1254
 
1226 1255
 [[package]]
1227 1256
 name = "quote"
1228
-version = "0.6.12"
1257
+version = "0.6.13"
1229 1258
 source = "registry+https://github.com/rust-lang/crates.io-index"
1230 1259
 dependencies = [
1231 1260
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
1232 1261
 ]
1233 1262
 
1234 1263
 [[package]]
1264
+name = "r2d2"
1265
+version = "0.8.5"
1266
+source = "registry+https://github.com/rust-lang/crates.io-index"
1267
+dependencies = [
1268
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
1269
+ "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
1270
+ "scheduled-thread-pool 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
1271
+]
1272
+
1273
+[[package]]
1235 1274
 name = "rand"
1236 1275
 version = "0.6.5"
1237 1276
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1379,14 +1418,6 @@ version = "0.1.56"
1379 1418
 source = "registry+https://github.com/rust-lang/crates.io-index"
1380 1419
 
1381 1420
 [[package]]
1382
-name = "redox_termios"
1383
-version = "0.1.1"
1384
-source = "registry+https://github.com/rust-lang/crates.io-index"
1385
-dependencies = [
1386
- "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
1387
-]
1388
-
1389
-[[package]]
1390 1421
 name = "regex"
1391 1422
 version = "1.1.9"
1392 1423
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1427,9 +1458,9 @@ dependencies = [
1427 1458
  "flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
1428 1459
  "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
1429 1460
  "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
1430
- "hyper 0.12.31 (registry+https://github.com/rust-lang/crates.io-index)",
1461
+ "hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)",
1431 1462
  "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
1432
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
1463
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
1433 1464
  "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
1434 1465
  "mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)",
1435 1466
  "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1462,9 +1493,9 @@ name = "rocket"
1462 1493
 version = "0.4.2"
1463 1494
 source = "registry+https://github.com/rust-lang/crates.io-index"
1464 1495
 dependencies = [
1465
- "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
1496
+ "atty 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
1466 1497
  "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
1467
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
1498
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
1468 1499
  "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
1469 1500
  "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
1470 1501
  "pear 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1484,7 +1515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1484 1515
 dependencies = [
1485 1516
  "devise 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
1486 1517
  "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
1487
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
1518
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
1488 1519
  "rocket_http 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
1489 1520
  "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
1490 1521
  "yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1496,7 +1527,7 @@ version = "0.4.2"
1496 1527
 source = "registry+https://github.com/rust-lang/crates.io-index"
1497 1528
 dependencies = [
1498 1529
  "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
1499
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
1530
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
1500 1531
  "notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
1501 1532
  "rocket 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
1502 1533
  "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1566,11 +1597,24 @@ dependencies = [
1566 1597
 ]
1567 1598
 
1568 1599
 [[package]]
1600
+name = "scheduled-thread-pool"
1601
+version = "0.2.1"
1602
+source = "registry+https://github.com/rust-lang/crates.io-index"
1603
+dependencies = [
1604
+ "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
1605
+]
1606
+
1607
+[[package]]
1569 1608
 name = "scopeguard"
1570 1609
 version = "0.3.3"
1571 1610
 source = "registry+https://github.com/rust-lang/crates.io-index"
1572 1611
 
1573 1612
 [[package]]
1613
+name = "scopeguard"
1614
+version = "1.0.0"
1615
+source = "registry+https://github.com/rust-lang/crates.io-index"
1616
+
1617
+[[package]]
1574 1618
 name = "security-framework"
1575 1619
 version = "0.3.1"
1576 1620
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1633,7 +1677,7 @@ version = "1.0.94"
1633 1677
 source = "registry+https://github.com/rust-lang/crates.io-index"
1634 1678
 dependencies = [
1635 1679
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
1636
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
1680
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
1637 1681
  "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
1638 1682
 ]
1639 1683
 
@@ -1729,7 +1773,7 @@ version = "0.15.39"
1729 1773
 source = "registry+https://github.com/rust-lang/crates.io-index"
1730 1774
 dependencies = [
1731 1775
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
1732
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
1776
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
1733 1777
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
1734 1778
 ]
1735 1779
 
@@ -1739,7 +1783,7 @@ version = "0.10.2"
1739 1783
 source = "registry+https://github.com/rust-lang/crates.io-index"
1740 1784
 dependencies = [
1741 1785
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
1742
- "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
1786
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
1743 1787
  "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
1744 1788
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
1745 1789
 ]
@@ -1778,17 +1822,6 @@ dependencies = [
1778 1822
 ]
1779 1823
 
1780 1824
 [[package]]
1781
-name = "termion"
1782
-version = "1.5.3"
1783
-source = "registry+https://github.com/rust-lang/crates.io-index"
1784
-dependencies = [
1785
- "libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
1786
- "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
1787
- "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
1788
- "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
1789
-]
1790
-
1791
-[[package]]
1792 1825
 name = "thread_local"
1793 1826
 version = "0.3.6"
1794 1827
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1859,7 +1892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1859 1892
 dependencies = [
1860 1893
  "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
1861 1894
  "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
1862
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
1895
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
1863 1896
 ]
1864 1897
 
1865 1898
 [[package]]
@@ -1870,7 +1903,7 @@ dependencies = [
1870 1903
  "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
1871 1904
  "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
1872 1905
  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
1873
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
1906
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
1874 1907
  "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
1875 1908
  "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
1876 1909
  "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1911,7 +1944,7 @@ dependencies = [
1911 1944
  "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
1912 1945
  "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
1913 1946
  "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
1914
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
1947
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
1915 1948
  "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
1916 1949
  "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
1917 1950
  "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2121,7 +2154,7 @@ version = "0.2.0"
2121 2154
 source = "registry+https://github.com/rust-lang/crates.io-index"
2122 2155
 dependencies = [
2123 2156
  "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
2124
- "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
2157
+ "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
2125 2158
  "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
2126 2159
 ]
2127 2160
 
@@ -2192,8 +2225,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2192 2225
 [metadata]
2193 2226
 "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
2194 2227
 "checksum aho-corasick 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36b7aa1ccb7d7ea3f437cf025a2ab1c47cc6c1bc9fc84918ff449def12f5e282"
2195
-"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
2196
-"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
2228
+"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
2229
+"checksum atty 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ecaaea69f52b3b18633611ec0007d188517d0366f47ff703d400fa6879d6f8d5"
2197 2230
 "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
2198 2231
 "checksum backtrace 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "18b50f5258d1a9ad8396d2d345827875de4261b158124d4c819d9b351454fae5"
2199 2232
 "checksum backtrace-sys 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "5b3a000b9c543553af61bc01cbfc403b04b5caa9e421033866f2e98061eb3e61"
@@ -2209,7 +2242,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2209 2242
 "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
2210 2243
 "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
2211 2244
 "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
2212
-"checksum cached 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24f3f5ca5651b8360fb859d6fd1b80fb4c25bc3cbf3ffc3a74d3d6a79fba328e"
2213 2245
 "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d"
2214 2246
 "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
2215 2247
 "checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe"
@@ -2257,12 +2289,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2257 2289
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
2258 2290
 "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
2259 2291
 "checksum h2 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "a539b63339fbbb00e081e84b6e11bd1d9634a82d91da2984a18ac74a8823f392"
2292
+"checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da"
2260 2293
 "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a"
2261 2294
 "checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
2262 2295
 "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
2263 2296
 "checksum humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
2264 2297
 "checksum hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273"
2265
-"checksum hyper 0.12.31 (registry+https://github.com/rust-lang/crates.io-index)" = "6481fff8269772d4463253ca83c788104a7305cb3fb9136bc651a6211e46e03f"
2298
+"checksum hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)" = "a64d71c1e77d39da024f06f5821ee00ad9c38febb90370bad1f07a94e0bc8793"
2266 2299
 "checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f"
2267 2300
 "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
2268 2301
 "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d"
@@ -2279,8 +2312,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2279 2312
 "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
2280 2313
 "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
2281 2314
 "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
2315
+"checksum lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff"
2282 2316
 "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
2283
-"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
2317
+"checksum log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c275b6ad54070ac2d665eef9197db647b32239c9d244bfb6f041a766d00da5b3"
2318
+"checksum lru 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "276235bb6b60773280b44b65e93815de82da5b6279ef175004fca03f4d06770a"
2284 2319
 "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43"
2285 2320
 "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
2286 2321
 "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
@@ -2302,15 +2337,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2302 2337
 "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
2303 2338
 "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
2304 2339
 "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
2305
-"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
2306
-"checksum once_cell 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "532c29a261168a45ce28948f9537ddd7a5dd272cc513b3017b1e82a88f962c37"
2307 2340
 "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409"
2308 2341
 "checksum openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)" = "97c140cbb82f3b3468193dd14c1b88def39f341f68257f8a7fe8ed9ed3f628a5"
2309 2342
 "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
2310 2343
 "checksum openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)" = "75bdd6dbbb4958d38e47a1d2348847ad1eb4dc205dc5d37473ae504391865acc"
2311 2344
 "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
2312 2345
 "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
2346
+"checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7"
2313 2347
 "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
2348
+"checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c"
2314 2349
 "checksum pear 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c26d2b92e47063ffce70d3e3b1bd097af121a9e0db07ca38a6cc1cf0cc85ff25"
2315 2350
 "checksum pear_codegen 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "336db4a192cc7f54efeb0c4e11a9245394824cc3bcbd37ba3ff51240c35d7a6e"
2316 2351
 "checksum pem 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2ae096568d61688cd1d419ca9815c94475d7b4622b1834d6e068e5bb2eb3d2"
@@ -2328,7 +2363,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2328 2363
 "checksum pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
2329 2364
 "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
2330 2365
 "checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d"
2331
-"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
2366
+"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
2367
+"checksum r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bc42ce75d9f4447fb2a04bbe1ed5d18dd949104572850ec19b164e274919f81b"
2332 2368
 "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
2333 2369
 "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
2334 2370
 "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
@@ -2345,7 +2381,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2345 2381
 "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
2346 2382
 "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
2347 2383
 "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
2348
-"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
2349 2384
 "checksum regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d9d8297cc20bbb6184f8b45ff61c8ee6a9ac56c156cec8e38c3e5084773c44ad"
2350 2385
 "checksum regex-syntax 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9b01330cce219c1c6b2e209e5ed64ccd587ae5c67bed91c0b49eecf02ae40e21"
2351 2386
 "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
@@ -2362,7 +2397,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2362 2397
 "checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9"
2363 2398
 "checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
2364 2399
 "checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339"
2400
+"checksum scheduled-thread-pool 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bbecfcb36d47e0d6a4aefb198d475b13aa06e326770c1271171d44893766ae1c"
2365 2401
 "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
2402
+"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
2366 2403
 "checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2"
2367 2404
 "checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56"
2368 2405
 "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
@@ -2387,7 +2424,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2387 2424
 "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
2388 2425
 "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
2389 2426
 "checksum tera 0.11.20 (registry+https://github.com/rust-lang/crates.io-index)" = "4b505279e19d8f7d24b1a9dc58327c9c36174b1a2c7ebdeac70792d017cb64f3"
2390
-"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330"
2391 2427
 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
2392 2428
 "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
2393 2429
 "checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"

+ 2
- 4
Cargo.toml View File

@@ -1,5 +1,3 @@
1
-cargo-features = ["default-run"]
2
-
3 1
 [package]
4 2
 name = "kibou"
5 3
 version = "0.1.0"
@@ -9,12 +7,12 @@ default-run = "kibou_server"
9 7
 [dependencies]
10 8
 base64 = "0.10.1"
11 9
 bcrypt = "0.5.0"
12
-cached = "0.9.0"
13 10
 chrono = "0.4.7"
14 11
 config = "0.9.3"
15
-diesel = { version = "1.4.2", features = ["chrono", "postgres", "serde_json"] }
12
+diesel = { version = "1.4.2", features = ["chrono", "postgres", "r2d2", "serde_json"] }
16 13
 getopts = "0.2.19"
17 14
 lazy_static = "1.3.0"
15
+lru = "0.1.15"
18 16
 openssl-sys = "0.9.47"
19 17
 openssl = "0.10.23"
20 18
 pem = "0.6.0"

+ 7
- 1
contrib/kibou.service View File

@@ -7,7 +7,13 @@ Description=Kibou server
7 7
 ; paths according to your Kibou installation
8 8
 ; ------------------------------------------------
9 9
 WorkingDirectory=/srv/kibou
10
-ExecStart=/srv/kibou/target/debug/kibou_server
10
+ExecStart=/srv/kibou/target/release/kibou_server
11
+; ------------------------------------------------
12
+
13
+; Change the the environment
14
+; -> Enum {production, development, staging}
15
+; ------------------------------------------------
16
+Environment="ROCKET_ENV=production"
11 17
 ; ------------------------------------------------
12 18
 
13 19
 ExecReload=/bin/kill $MAINPID

+ 4
- 0
env.toml.sample View File

@@ -17,6 +17,10 @@ base_scheme = "https"
17 17
 host = "localhost"
18 18
 port = 8000
19 19
 
20
+# Adjust the workers value 
21
+# -> Number of CPU cores * 2
22
+workers = 2
23
+
20 24
 [node]
21 25
 name = "Kibou"
22 26
 description = "A Kibou instance"

+ 2
- 2
migrations/2019-06-23-131810_notifications/up.sql View File

@@ -1,7 +1,7 @@
1 1
 CREATE TABLE notifications (
2 2
 	id BIGSERIAL PRIMARY KEY,
3
-	activity_id BIGSERIAL REFERENCES activities(id),
4
-	actor_id BIGSERIAL REFERENCES actors(id),
3
+	activity_id BIGSERIAL REFERENCES activities(id) ON DELETE CASCADE,
4
+	actor_id BIGSERIAL REFERENCES actors(id) ON DELETE CASCADE,
5 5
 	created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
6 6
 	modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
7 7
 );

+ 43
- 21
src/activity.rs View File

@@ -1,4 +1,4 @@
1
-use database::models::{InsertActivity, QueryActivity};
1
+use database::models::{InsertActivity, QueryActivity, QueryActivityId};
2 2
 use database::runtime_escape;
3 3
 use database::schema::activities;
4 4
 use database::schema::activities::dsl::*;
@@ -16,15 +16,23 @@ pub struct Activity {
16 16
     pub actor: String,
17 17
 }
18 18
 
19
+// Beware: This module depends on a lot of raw queries, which we should deprecate in the future. The
20
+// only reason they're being used is because Diesel.rs does not support JSONB operators that are
21
+// needed:
22
+// {->, ->>, @>, ?}
23
+//
24
+// Related issue: https://git.cybre.club/kibouproject/kibou/issues/32
25
+// Diesel.rs issue: https://github.com/diesel-rs/diesel/issues/44
26
+
19 27
 pub fn count_ap_object_replies_by_id(
20 28
     db_connection: &PgConnection,
21 29
     object_id: &str,
22 30
 ) -> Result<usize, diesel::result::Error> {
23 31
     match sql_query(format!(
24
-        "SELECT * FROM activities WHERE data @> '{{\"object\": {{\"inReplyTo\": \"{}\"}}}}';",
32
+        "SELECT id FROM activities WHERE data @> '{{\"object\": {{\"inReplyTo\": \"{}\"}}}}';",
25 33
         runtime_escape(object_id)
26 34
     ))
27
-    .load::<QueryActivity>(db_connection)
35
+    .load::<QueryActivityId>(db_connection)
28 36
     {
29 37
         Ok(activity_arr) => Ok(activity_arr.len()),
30 38
         Err(e) => Err(e),
@@ -37,12 +45,12 @@ pub fn count_ap_object_reactions_by_id(
37 45
     reaction: &str,
38 46
 ) -> Result<usize, diesel::result::Error> {
39 47
     match sql_query(format!(
40
-        "SELECT * FROM activities WHERE data @> '{{\"type\": \"{reaction_type}\"}}' \
48
+        "SELECT id FROM activities WHERE data @> '{{\"type\": \"{reaction_type}\"}}' \
41 49
          AND data @> '{{\"object\": \"{id}\"}}';",
42 50
         reaction_type = runtime_escape(reaction),
43 51
         id = runtime_escape(object_id)
44 52
     ))
45
-    .load::<QueryActivity>(db_connection)
53
+    .load::<QueryActivityId>(db_connection)
46 54
     {
47 55
         Ok(activity_arr) => Ok(activity_arr.len()),
48 56
         Err(e) => Err(e),
@@ -54,16 +62,16 @@ pub fn count_ap_notes_for_actor(
54 62
     actor: &str,
55 63
 ) -> Result<usize, diesel::result::Error> {
56 64
     match sql_query(format!(
57
-        "SELECT * \
65
+        "SELECT id \
58 66
          FROM activities \
59
-         WHERE data->>'type' = 'Create' \
60
-         AND data->'object'->>'type' = 'Note' \
61
-         AND data->>'actor' = '{actor}' \
67
+         WHERE data @> '{{\"actor\": \"{actor}\", \
68
+         \"type\": \"Create\", \
69
+         \"object\": {{\"type\": \"Note\"}}}}' \
62 70
          AND ((data->>'to')::jsonb ? 'https://www.w3.org/ns/activitystreams#Public' \
63 71
          OR (data->>'cc')::jsonb ? 'https://www.w3.org/ns/activitystreams#Public');",
64 72
         actor = runtime_escape(actor)
65 73
     ))
66
-    .load::<QueryActivity>(db_connection)
74
+    .load::<QueryActivityId>(db_connection)
67 75
     {
68 76
         Ok(activity_arr) => Ok(activity_arr.len()),
69 77
         Err(e) => Err(e),
@@ -72,7 +80,7 @@ pub fn count_ap_notes_for_actor(
72 80
 
73 81
 pub fn count_local_ap_notes(db_connection: &PgConnection) -> Result<usize, diesel::result::Error> {
74 82
     match sql_query(format!(
75
-        "SELECT * \
83
+        "SELECT id \
76 84
          FROM activities \
77 85
          WHERE data->>'type' = 'Create' \
78 86
          AND data->'object'->>'type' = 'Note' \
@@ -83,13 +91,32 @@ pub fn count_local_ap_notes(db_connection: &PgConnection) -> Result<usize, diese
83 91
         base_domain = env::get_value(String::from("endpoint.base_domain"))
84 92
     ))
85 93
     .clone()
86
-    .load::<QueryActivity>(db_connection)
94
+    .load::<QueryActivityId>(db_connection)
87 95
     {
88 96
         Ok(activity_arr) => Ok(activity_arr.len()),
89 97
         Err(e) => Err(e),
90 98
     }
91 99
 }
92 100
 
101
+pub fn get_activities_by_id(
102
+    db_connection: &PgConnection,
103
+    ids: Vec<i64>,
104
+) -> Result<Vec<Activity>, diesel::result::Error> {
105
+    let parsed_ids: Vec<String> = ids.iter().map(|num| num.to_string()).collect();
106
+    match sql_query(format!(
107
+        "SELECT * FROM activities WHERE id = ANY(ARRAY[{}]);",
108
+        parsed_ids.join(", ")
109
+    ))
110
+    .load::<QueryActivity>(db_connection)
111
+    {
112
+        Ok(activity_arr) => Ok(activity_arr
113
+            .iter()
114
+            .map(|activity| serialize_activity(activity.clone()))
115
+            .collect()),
116
+        Err(e) => Err(e),
117
+    }
118
+}
119
+
93 120
 pub fn get_activity_by_id(
94 121
     db_connection: &PgConnection,
95 122
     activity_id: i64,
@@ -159,15 +186,10 @@ pub fn get_ap_object_replies_by_id(
159 186
     ))
160 187
     .load::<QueryActivity>(db_connection)
161 188
     {
162
-        Ok(activity) => {
163
-            let mut serialized_activites: Vec<Activity> = vec![];
164
-
165
-            for object in activity {
166
-                serialized_activites.push(serialize_activity(object));
167
-            }
168
-
169
-            Ok(serialized_activites)
170
-        }
189
+        Ok(activity_arr) => Ok(activity_arr
190
+            .iter()
191
+            .map(|activity| serialize_activity(activity.clone()))
192
+            .collect()),
171 193
         Err(e) => Err(e),
172 194
     }
173 195
 }

+ 24
- 1
src/activitypub/mod.rs View File

@@ -5,6 +5,7 @@ pub mod routes;
5 5
 pub mod validator;
6 6
 
7 7
 use base64;
8
+use rocket::data::{self, Data, FromDataSimple};
8 9
 use rocket::http::ContentType;
9 10
 use rocket::http::MediaType;
10 11
 use rocket::http::Status;
@@ -13,7 +14,7 @@ use rocket::response::{self, Responder, Response};
13 14
 use rocket::Outcome;
14 15
 use serde::{Deserialize, Serialize};
15 16
 use std::collections::HashMap;
16
-use std::io::Cursor;
17
+use std::io::{Cursor, Read};
17 18
 use web::http_signatures::Signature;
18 19
 
19 20
 pub struct ActivitypubMediatype(bool);
@@ -31,6 +32,28 @@ pub struct Attachment {
31 32
     pub mediaType: Option<String>,
32 33
 }
33 34
 
35
+pub struct Payload(serde_json::Value);
36
+
37
+impl FromDataSimple for Payload {
38
+    type Error = String;
39
+
40
+    fn from_data(req: &Request, data: Data) -> data::Outcome<Self, String> {
41
+        let mut data_stream = String::new();
42
+
43
+        // Read at most a 1MB payload
44
+        //
45
+        // TODO: This value should be adjustable in the config
46
+        if let Err(e) = data.open().take(1048576).read_to_string(&mut data_stream) {
47
+            return Outcome::Failure((Status::InternalServerError, format!("{:?}", e)));
48
+        }
49
+
50
+        match serde_json::from_str(&data_stream) {
51
+            Ok(value) => return Outcome::Success(Payload(value)),
52
+            Err(e) => return Outcome::Failure((Status::UnprocessableEntity, format!("{:?}", e))),
53
+        }
54
+    }
55
+}
56
+
34 57
 impl<'a, 'r> FromRequest<'a, 'r> for ActivitypubMediatype {
35 58
     type Error = ();
36 59
 

+ 5
- 16
src/activitypub/routes.rs View File

@@ -3,6 +3,7 @@ use activitypub::actor as ap_actor;
3 3
 use activitypub::controller;
4 4
 use activitypub::ActivitypubMediatype;
5 5
 use activitypub::ActivitystreamsResponse;
6
+use activitypub::Payload;
6 7
 use activitypub::Signature;
7 8
 use rocket::http::Status;
8 9
 use serde_json;
@@ -18,25 +19,13 @@ pub fn actor(_media_type: ActivitypubMediatype, handle: String) -> Activitystrea
18 19
 }
19 20
 
20 21
 #[post("/actors/<id>/inbox", data = "<activity>")]
21
-pub fn actor_inbox(id: String, activity: String, _signature: Signature) -> Status {
22
-    match serde_json::from_str(&activity) {
23
-        Ok(serialized_activity) => {
24
-            controller::prepare_incoming(serialized_activity, _signature);
25
-            return rocket::http::Status::Ok;
26
-        }
27
-        Err(_) => return rocket::http::Status::BadRequest,
28
-    }
22
+pub fn actor_inbox(id: String, activity: Payload, _signature: Signature) {
23
+    controller::prepare_incoming(activity.0, _signature);
29 24
 }
30 25
 
31 26
 #[post("/inbox", data = "<activity>")]
32
-pub fn inbox(activity: String, _signature: Signature) -> Status {
33
-    match serde_json::from_str(&activity) {
34
-        Ok(serialized_activity) => {
35
-            controller::prepare_incoming(serialized_activity, _signature);
36
-            return rocket::http::Status::Ok;
37
-        }
38
-        Err(_) => return rocket::http::Status::BadRequest,
39
-    }
27
+pub fn inbox(activity: Payload, _signature: Signature) {
28
+    controller::prepare_incoming(activity.0, _signature);
40 29
 }
41 30
 
42 31
 #[get("/objects/<id>")]

+ 5
- 5
src/bin/kibou_server.rs View File

@@ -4,16 +4,16 @@ use kibou::env;
4 4
 use kibou::rocket_app;
5 5
 
6 6
 fn main() {
7
-    // TODO: Determine the environment Rocket is running in (ROCKET_ENV)
8
-    // We are currently just assuming a development enviroment
9
-
10
-    let rocket_config = rocket::config::Config::build(rocket::config::Environment::Development)
7
+    let rocket_config = rocket::config::Config::build(rocket::config::Environment::active().expect("Unknown ROCKET_ENV value! (enum: {Development, Staging, Production})"))
11 8
         .address(env::get_value("endpoint.host".to_string()))
12 9
         .port(
13 10
             env::get_value("endpoint.port".to_string())
14 11
                 .parse::<u16>()
15 12
                 .unwrap(),
16
-        );
13
+        )
14
+        .workers(env::get_value("endpoint.workers".to_string())
15
+                .parse::<u16>()
16
+                .unwrap_or_else(|_| 2));
17 17
 
18 18
     unsafe {
19 19
         kibou::raito_fe::BYPASS_API = &true;

+ 52
- 10
src/database/mod.rs View File

@@ -1,22 +1,54 @@
1 1
 pub mod models;
2 2
 pub mod schema;
3
-
4 3
 use diesel::pg::PgConnection;
5 4
 use diesel::prelude::*;
5
+use diesel::r2d2;
6
+use diesel::r2d2::ConnectionManager;
6 7
 use env;
7 8
 use regex::Regex;
9
+use rocket::http::Status;
10
+use rocket::request::{self, FromRequest};
11
+use rocket::{Outcome, Request, State};
12
+
13
+pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
14
+pub struct PooledConnection(pub r2d2::PooledConnection<ConnectionManager<PgConnection>>);
15
+
16
+lazy_static! {
17
+    pub static ref POOL: Pool = initialize_pool();
18
+}
19
+
20
+impl<'a, 'r> FromRequest<'a, 'r> for PooledConnection {
21
+    type Error = ();
22
+    fn from_request(request: &'a Request<'r>) -> request::Outcome<PooledConnection, Self::Error> {
23
+        match POOL.get() {
24
+            Ok(db_connection) => Outcome::Success(PooledConnection(db_connection)),
25
+            Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
26
+        }
27
+    }
28
+}
8 29
 
30
+impl std::ops::Deref for PooledConnection {
31
+    type Target = PgConnection;
32
+    fn deref(&self) -> &Self::Target {
33
+        return &self.0;
34
+    }
35
+}
36
+
37
+#[deprecated]
9 38
 pub fn establish_connection() -> PgConnection {
10
-    let database_url = format!(
11
-        "postgres://{username}:{password}@{host}/{database}",
12
-        username = env::get_value("database.username".to_string()),
13
-        password = env::get_value("database.password".to_string()),
14
-        host = env::get_value("database.hostname".to_string()),
15
-        database = env::get_value("database.database".to_string())
16
-    );
39
+    PgConnection::establish(&prepare_postgres_url()).unwrap_or_else(|_| {
40
+        panic!(format!(
41
+            "Could not connect to {url}",
42
+            url = &prepare_postgres_url()
43
+        ))
44
+    })
45
+}
17 46
 
18
-    PgConnection::establish(&database_url)
19
-        .unwrap_or_else(|_| panic!(format!("Could not connect to {url}", url = &database_url)))
47
+pub fn initialize_pool() -> Pool {
48
+    let connection_manager = ConnectionManager::<PgConnection>::new(prepare_postgres_url());
49
+
50
+    // TODO: Eventually replace this with r2d2::Pool::builder()
51
+    return Pool::new(connection_manager).expect("Could not initialize database pool!");
20 52
 }
21 53
 
22 54
 pub fn runtime_escape(value: &str) -> String {
@@ -26,3 +58,13 @@ pub fn runtime_escape(value: &str) -> String {
26 58
         .filter(|&c| escape_regex.is_match(&c.to_string()))
27 59
         .collect()
28 60
 }
61
+
62
+fn prepare_postgres_url() -> String {
63
+    return format!(
64
+        "postgres://{username}:{password}@{host}/{database}",
65
+        username = env::get_value("database.username".to_string()),
66
+        password = env::get_value("database.password".to_string()),
67
+        host = env::get_value("database.hostname".to_string()),
68
+        database = env::get_value("database.database".to_string())
69
+    );
70
+}

+ 14
- 8
src/env.rs View File

@@ -1,20 +1,26 @@
1
-extern crate config;
1
+use rocket::config::Environment;
2 2
 
3 3
 pub fn get_value(key: String) -> String {
4 4
     let mut config = config::Config::default();
5
+    let environment = match Environment::active() {
6
+        Ok(Environment::Development) => "development",
7
+        Ok(Environment::Staging) => "staging",
8
+        Ok(Environment::Production) => "production",
9
+        Err(_) => "development"
10
+    };
5 11
 
6 12
     set_default_config_values(&mut config);
7 13
 
8
-    // TODO: Find config file based on ROCKET_ENV
9 14
     config
10
-        .merge(config::File::with_name("env.development.toml"))
15
+        .merge(config::File::with_name(&format!("env.{}.toml", environment)))
11 16
         .expect("Environment config not found!");
12 17
 
13
-    if config.get_str(&key).is_ok() {
14
-        config.get_str(&key).ok().unwrap()
15
-    } else {
16
-        eprintln!("Key '{}' in environment config not found", &key);
17
-        String::from("")
18
+    match config.get_str(&key) {
19
+        Ok(value) => value,
20
+        Err(_) => {
21
+            eprintln!("Key '{}' not found in config", &key);
22
+            return String::from("");
23
+        }
18 24
     }
19 25
 }
20 26
 

+ 6
- 0
src/html.rs View File

@@ -1,5 +1,11 @@
1 1
 use regex::Regex;
2 2
 
3
+pub fn to_plain_text(input: &str) -> String {
4
+    let output = str::replace(&input, "\n", "<br>");
5
+
6
+    return strip_tags(&output);
7
+}
8
+
3 9
 pub fn strip_tags(input: &str) -> String {
4 10
     let allowed_tags = vec!["a", "b", "br", "em", "img", "strong", "u"];
5 11
     let forbidden_attributes = vec![

+ 7
- 16
src/kibou_api/mod.rs View File

@@ -12,9 +12,9 @@ use activity::{
12 12
 use activitypub::activity::{serialize_from_internal_activity, Tag};
13 13
 use activitypub::actor::{add_follow, remove_follow};
14 14
 use activitypub::controller as ap_controller;
15
-
16 15
 use actor::{get_actor_by_acct, get_actor_by_id, get_actor_by_uri, is_actor_followed_by, Actor};
17 16
 use database;
17
+use database::PooledConnection;
18 18
 use diesel::PgConnection;
19 19
 use html;
20 20
 use mastodon_api;
@@ -121,8 +121,11 @@ pub fn react(actor: &i64, _type: &str, object_id: &str) {
121 121
     }
122 122
 }
123 123
 
124
-pub fn route_activities() -> JsonValue {
125
-    return json!(public_activities());
124
+pub fn public_activities(pooled_connection: &PooledConnection) -> JsonValue {
125
+    match timeline::public_activities(pooled_connection) {
126
+        Ok(activities) => mastodon_api::controller::cached_statuses(pooled_connection, activities),
127
+        Err(_) => json!({"error": "An error occured while querying public activities"}),
128
+    }
126 129
 }
127 130
 
128 131
 pub fn status_build(
@@ -140,7 +143,7 @@ pub fn status_build(
140 143
     let mut tags: Vec<serde_json::Value> = Vec::new();
141 144
     let mut in_reply_to_id: Option<String>;
142 145
 
143
-    let parsed_mentions = parse_mentions(html::strip_tags(&content));
146
+    let parsed_mentions = parse_mentions(html::to_plain_text(&content));
144 147
     direct_receipients.extend(parsed_mentions.0);
145 148
     inboxes.extend(parsed_mentions.1);
146 149
     tags.extend(parsed_mentions.2);
@@ -335,15 +338,3 @@ fn parse_mentions(content: String) -> (Vec<String>, Vec<String>, Vec<serde_json:
335 338
     }
336 339
     (receipients, inboxes, tags, new_content)
337 340
 }
338
-
339
-fn public_activities() -> Vec<mastodon_api::Status> {
340
-    let database = database::establish_connection();
341
-
342
-    match timeline::public_activities(&database) {
343
-        Ok(activities) => activities
344
-            .iter()
345
-            .map(|activity| mastodon_api::controller::status_cached_by_id(*activity).unwrap())
346
-            .collect(),
347
-        Err(_) => Vec::new(),
348
-    }
349
-}

+ 3
- 2
src/kibou_api/routes.rs View File

@@ -1,7 +1,8 @@
1
+use database::PooledConnection;
1 2
 use kibou_api;
2 3
 use rocket_contrib::json::JsonValue;
3 4
 
4 5
 #[get("/api/kibou/activities")]
5
-pub fn activities() -> JsonValue {
6
-    return kibou_api::route_activities();
6
+pub fn activities(pooled_connection: PooledConnection) -> JsonValue {
7
+    return kibou_api::public_activities(&pooled_connection);
7 8
 }

+ 4
- 2
src/lib.rs View File

@@ -3,17 +3,19 @@
3 3
 
4 4
 extern crate base64;
5 5
 extern crate bcrypt;
6
-#[macro_use]
7
-extern crate cached;
8 6
 extern crate chrono;
9 7
 #[macro_use]
10 8
 extern crate diesel;
9
+#[macro_use]
10
+extern crate lazy_static;
11
+extern crate lru;
11 12
 extern crate openssl;
12 13
 extern crate pem;
13 14
 extern crate regex;
14 15
 extern crate reqwest;
15 16
 #[macro_use]
16 17
 extern crate rocket;
18
+extern crate core;
17 19
 extern crate rocket_contrib;
18 20
 extern crate serde;
19 21
 extern crate serde_json;

+ 251
- 532
src/mastodon_api/controller.rs View File

@@ -1,19 +1,26 @@
1 1
 use activity;
2
-use activity::{get_ap_object_by_id, get_ap_object_replies_by_id, type_exists_for_object_id};
2
+use activity::{
3
+    get_activities_by_id, get_ap_object_by_id, get_ap_object_replies_by_id,
4
+    type_exists_for_object_id,
5
+};
3 6
 use activitypub;
4 7
 use actor;
5 8
 use actor::get_actor_by_id;
6 9
 use actor::get_actor_by_uri;
7
-use cached::TimedCache;
8 10
 use chrono;
9 11
 use chrono::Utc;
12
+use core::borrow::Borrow;
10 13
 use database;
14
+use database::PooledConnection;
11 15
 use diesel::PgConnection;
12 16
 use env;
13 17
 use kibou_api;
18
+use lru::LruCache;
19
+use mastodon_api::routes::status;
14 20
 use mastodon_api::{
15 21
     Account, Attachment, HomeTimeline, Instance, Notification, PublicTimeline, RegistrationForm,
16
-    Relationship, Source, Status, StatusForm,
22
+    Relationship, Source, Status, StatusForm, MASTODON_API_ACCOUNT_CACHE,
23
+    MASTODON_API_NOTIFICATION_CACHE, MASTODON_API_STATUS_CACHE,
17 24
 };
18 25
 use notification::notifications_for_actor;
19 26
 use oauth;
@@ -25,47 +32,26 @@ use rocket_contrib::json::JsonValue;
25 32
 use timeline;
26 33
 use timeline::{home_timeline as get_home_timeline, public_timeline as get_public_timeline};
27 34
 
28
-pub fn account(id: i64) -> JsonValue {
29
-    let database = database::establish_connection();
30
-
31
-    match actor::get_actor_by_id(&database, &id) {
32
-        Ok(actor) => json!(serialize_account(actor, false)),
35
+pub fn account(pooled_connection: &PooledConnection, id: i64) -> JsonValue {
36
+    match actor::get_actor_by_id(pooled_connection, &id) {
37
+        Ok(actor) => json!(Account::from_actor(pooled_connection, actor, false)),
33 38
         Err(_) => json!({"error": "User not found."}),
34 39
     }
35 40
 }
36 41
 
37
-pub fn account_by_oauth_token(token: String) -> Result<Account, diesel::result::Error> {
38
-    let database = database::establish_connection();
39
-
40
-    match verify_token(&database, token) {
41
-        Ok(token) => match actor::get_local_actor_by_preferred_username(&database, &token.actor) {
42
-            Ok(actor) => Ok(serialize_account(actor, true)),
43
-            Err(e) => Err(e),
44
-        },
45
-        Err(e) => Err(e),
46
-    }
47
-}
48
-
49
-pub fn account_json_by_oauth_token(token: String) -> JsonValue {
50
-    let database = database::establish_connection();
51
-
52
-    match verify_token(&database, token) {
53
-        Ok(token) => match actor::get_local_actor_by_preferred_username(&database, &token.actor) {
54
-            Ok(actor) => json!(serialize_account(actor, true)),
55
-            Err(_) => json!({"error": "No user is associated to this token!"}),
56
-        },
42
+pub fn account_by_oauth_token(pooled_connection: &PooledConnection, token: String) -> JsonValue {
43
+    match verify_token(pooled_connection, token) {
44
+        Ok(token) => {
45
+            match actor::get_local_actor_by_preferred_username(pooled_connection, &token.actor) {
46
+                Ok(actor) => json!(Account::from_actor(pooled_connection, actor, true)),
47
+                Err(_) => json!({"error": "No user is associated to this token!"}),
48
+            }
49
+        }
57 50
         Err(_) => json!({"error": "Token invalid!"}),
58 51
     }
59 52
 }
60 53
 
61
-pub fn account_create_json(form: &RegistrationForm) -> JsonValue {
62
-    match account_create(form) {
63
-        Some(token) => serde_json::to_value(token).unwrap().into(),
64
-        None => json!({"error": "Account could not be created!"}),
65
-    }
66
-}
67
-
68
-pub fn account_create(form: &RegistrationForm) -> Option<Token> {
54
+pub fn account_create(form: &RegistrationForm) -> JsonValue {
69 55
     let email_regex = Regex::new(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$").unwrap();
70 56
     let username_regex = Regex::new(r"^[A-Za-z0-9_]{1,32}$").unwrap();
71 57
 
@@ -96,34 +82,27 @@ pub fn account_create(form: &RegistrationForm) -> Option<Token> {
96 82
         actor::create_actor(&database, &mut new_actor);
97 83
 
98 84
         match actor::get_local_actor_by_preferred_username(&database, &form.username) {
99
-            Ok(_actor) => Some(oauth::token::create(&form.username)),
100
-            Err(_) => None,
85
+            Ok(_actor) => json!(oauth::token::create(&form.username)),
86
+            Err(_) => json!({"error": "Account could not be created"}),
101 87
         }
102 88
     } else {
103
-        return None;
89
+        return json!({"error": "Username or E-mail contains unsupported characters"});
104 90
     }
105 91
 }
106 92
 
107
-pub fn account_statuses_json_by_id(
93
+pub fn account_statuses_by_id(
94
+    pooled_connection: &PooledConnection,
108 95
     id: i64,
109 96
     max_id: Option<i64>,
110 97
     since_id: Option<i64>,
111 98
     min_id: Option<i64>,
112 99
     limit: Option<i64>,
113 100
 ) -> JsonValue {
114
-    let database = database::establish_connection();
115
-
116
-    match actor::get_actor_by_id(&database, &id) {
101
+    match actor::get_actor_by_id(pooled_connection, &id) {
117 102
         Ok(actor) => {
118
-            match timeline::user_timeline(&database, actor, max_id, since_id, min_id, limit) {
119
-                Ok(statuses) => {
120
-                    let status_vec: Vec<Status> = statuses
121
-                        .iter()
122
-                        .filter(|&id| status_cached_by_id(*id).is_ok())
123
-                        .map(|id| status_cached_by_id(*id).unwrap())
124
-                        .collect();
125
-                    return json!(status_vec);
126
-                }
103
+            match timeline::user_timeline(pooled_connection, actor, max_id, since_id, min_id, limit)
104
+            {
105
+                Ok(statuses) => cached_statuses(pooled_connection, statuses),
127 106
                 Err(_) => json!({"error": "Error generating user timeline."}),
128 107
             }
129 108
         }
@@ -131,9 +110,11 @@ pub fn account_statuses_json_by_id(
131 110
     }
132 111
 }
133 112
 
134
-pub fn application_create(application: OAuthApplication) -> rocket_contrib::json::JsonValue {
135
-    let database = database::establish_connection();
136
-    let oauth_app: OAuthApplication = oauth::application::create(&database, application);
113
+pub fn application_create(
114
+    pooled_connection: &PooledConnection,
115
+    application: OAuthApplication,
116
+) -> JsonValue {
117
+    let oauth_app: OAuthApplication = oauth::application::create(pooled_connection, application);
137 118
     rocket_contrib::json!({
138 119
         "name": oauth_app.client_name.unwrap_or_default(),
139 120
         "website": oauth_app.website,
@@ -144,13 +125,95 @@ pub fn application_create(application: OAuthApplication) -> rocket_contrib::json
144 125
     })
145 126
 }
146 127
 
147
-pub fn context_json_for_id(id: i64) -> JsonValue {
148
-    let database = database::establish_connection();
128
+pub fn cached_account(pooled_connection: &PooledConnection, uri: &str) -> JsonValue {
129
+    let mut account_cache = MASTODON_API_ACCOUNT_CACHE.lock().unwrap();
130
+
131
+    if account_cache.contains(&uri.to_string()) {
132
+        return json!(account_cache.get(&uri.to_string()));
133
+    } else {
134
+        match actor::get_actor_by_uri(pooled_connection, uri) {
135
+            Ok(actor) => {
136
+                let result =
137
+                    serde_json::json!(Account::from_actor(pooled_connection, actor, false));
138
+                account_cache.put(uri.to_string(), result.clone());
139
+                return json!(result);
140
+            }
141
+            Err(_) => json!({ "error": format!("Account not found: {}", uri) }),
142
+        }
143
+    }
144
+}
149 145
 
150
-    match activity::get_activity_by_id(&database, id) {
146
+pub fn cached_notifications(pooled_connection: &PooledConnection, ids: Vec<i64>) -> JsonValue {
147
+    let mut notification_cache = MASTODON_API_NOTIFICATION_CACHE.lock().unwrap();
148
+    let mut notifications: Vec<Notification> = Vec::new();
149
+    let mut uncached_notifications: Vec<i64> = Vec::new();
150
+    for id in ids {
151
+        if notification_cache.contains(&id) {
152
+            notifications.push(
153
+                serde_json::from_value(notification_cache.get(&id).unwrap().clone()).unwrap(),
154
+            );
155
+        } else {
156
+            uncached_notifications.push(id);
157
+        }
158
+    }
159
+
160
+    match get_activities_by_id(pooled_connection, uncached_notifications) {
161
+        Ok(activities) => {
162
+            for activity in activities {
163
+                match Notification::try_from(activity) {
164
+                    Ok(notification) => {
165
+                        notification_cache.put(
166
+                            notification.id.parse::<i64>().unwrap(),
167
+                            serde_json::json!(notification),
168
+                        );
169
+                        notifications.push(notification);
170
+                    }
171
+                    Err(_) => (),
172
+                }
173
+            }
174
+        }
175
+        Err(_) => (),
176
+    }
177
+    notifications.sort_by(|a, b| b.id.cmp(&a.id));
178
+    return json!(notifications);
179
+}
180
+
181
+pub fn cached_statuses(pooled_connection: &PooledConnection, ids: Vec<i64>) -> JsonValue {
182
+    let mut status_cache = MASTODON_API_STATUS_CACHE.lock().unwrap();
183
+    let mut statuses: Vec<Status> = Vec::new();
184
+    let mut uncached_statuses: Vec<i64> = Vec::new();
185
+    for id in ids {
186
+        if status_cache.contains(&id) {
187
+            statuses.push(serde_json::from_value(status_cache.get(&id).unwrap().clone()).unwrap());
188
+        } else {
189
+            uncached_statuses.push(id);
190
+        }
191
+    }
192
+
193
+    match get_activities_by_id(pooled_connection, uncached_statuses) {
194
+        Ok(activities) => {
195
+            for activity in activities {
196
+                match Status::try_from(activity) {
197
+                    Ok(status) => {
198
+                        status_cache
199
+                            .put(status.id.parse::<i64>().unwrap(), serde_json::json!(status));
200
+                        statuses.push(status);
201
+                    }
202
+                    Err(_) => (),
203
+                }
204
+            }
205
+        }
206
+        Err(_) => (),
207
+    }
208
+    statuses.sort_by(|a, b| b.id.cmp(&a.id));
209
+    return json!(statuses);
210
+}
211
+
212
+pub fn context_json_for_id(pooled_connection: &PooledConnection, id: i64) -> JsonValue {
213
+    match activity::get_activity_by_id(&pooled_connection, id) {
151 214
         Ok(_activity) => {
152
-            let mut ancestors = status_parents_for_id(&database, id, true);
153
-            let mut descendants = status_children_for_id(&database, id, true);
215
+            let mut ancestors = status_parents_for_id(pooled_connection, id, true);
216
+            let mut descendants = status_children_for_id(pooled_connection, id, true);
154 217
             ancestors.sort_by(|status_a, status_b| {
155 218
                 chrono::DateTime::parse_from_rfc3339(&status_a.created_at)
156 219
                     .unwrap_or_else(|_| {
@@ -187,21 +250,23 @@ pub fn context_json_for_id(id: i64) -> JsonValue {
187 250
     }
188 251
 }
189 252
 
190
-pub fn favourite(token: String, id: i64) -> JsonValue {
191
-    let database = database::establish_connection();
192
-
193
-    match activity::get_activity_by_id(&database, id) {
194
-        Ok(activity) => match account_by_oauth_token(token) {
195
-            Ok(account) => {
196
-                kibou_api::react(
197
-                    &account.id.parse::<i64>().unwrap(),
198
-                    "Like",
199
-                    activity.data["object"]["id"].as_str().unwrap(),
200
-                );
201
-                json!(status_cached_by_id(id))
253
+pub fn favourite(pooled_connection: &PooledConnection, token: String, id: i64) -> JsonValue {
254
+    match activity::get_activity_by_id(pooled_connection, id) {
255
+        Ok(activity) => {
256
+            let account: Result<Account, serde_json::Error> =
257
+                serde_json::from_value(account_by_oauth_token(pooled_connection, token).into());
258
+            match account {
259
+                Ok(account) => {
260
+                    kibou_api::react(
261
+                        &account.id.parse::<i64>().unwrap(),
262
+                        "Like",
263
+                        activity.data["object"]["id"].as_str().unwrap(),
264
+                    );
265
+                    return status_by_id(pooled_connection, id);
266
+                }
267
+                Err(_) => json!({"error": "Token invalid!"}),
202 268
             }
203
-            Err(_) => json!({"error": "Token invalid!"}),
204
-        },
269
+        }
205 270
         Err(_) => json!({"error": "Status not found"}),
206 271
     }
207 272
 }
@@ -231,33 +296,32 @@ pub fn follow(token: String, id: i64) -> JsonValue {
231 296
     }
232 297
 }
233 298
 
234
-pub fn home_timeline(parameters: HomeTimeline, token: String) -> JsonValue {
235
-    let database = database::establish_connection();
236
-
237
-    match verify_token(&database, token) {
238
-        Ok(token) => match actor::get_local_actor_by_preferred_username(&database, &token.actor) {
239
-            Ok(actor) => {
240
-                match get_home_timeline(
241
-                    &database,
242
-                    actor,
243
-                    parameters.max_id,
244
-                    parameters.since_id,
245
-                    parameters.min_id,
246
-                    parameters.limit,
247
-                ) {
248
-                    Ok(statuses) => {
249
-                        let status_vec: Vec<Status> = statuses
250
-                            .iter()
251
-                            .filter(|&id| status_cached_by_id(*id).is_ok())
252
-                            .map(|id| status_cached_by_id(*id).unwrap())
253
-                            .collect();
254
-                        return json!(status_vec);
299
+pub fn home_timeline(
300
+    pooled_connection: &PooledConnection,
301
+    parameters: HomeTimeline,
302
+    token: String,
303
+) -> JsonValue {
304
+    match verify_token(pooled_connection, token) {
305
+        Ok(token) => {
306
+            match actor::get_local_actor_by_preferred_username(pooled_connection, &token.actor) {
307
+                Ok(actor) => {
308
+                    match get_home_timeline(
309
+                        pooled_connection,
310
+                        actor,
311
+                        parameters.max_id,
312
+                        parameters.since_id,
313
+                        parameters.min_id,
314
+                        parameters.limit,
315
+                    ) {
316
+                        Ok(statuses) => cached_statuses(pooled_connection, statuses),
317
+                        Err(_e) => {
318
+                            json!({"error": "An error occured while generating home timeline"})
319
+                        }
255 320
                     }
256
-                    Err(_e) => json!({"error": "An error occured while generating home timeline"}),
257 321
                 }
322
+                Err(_e) => json!({"error": "User associated to token not found"}),
258 323
             }
259
-            Err(_e) => json!({"error": "User associated to token not found"}),
260
-        },
324
+        }
261 325
         Err(_e) => json!({"error": "Invalid oauth token"}),
262 326
     }
263 327
 }
@@ -287,41 +351,44 @@ pub fn instance_info() -> JsonValue {
287 351
     })
288 352
 }
289 353
 
290
-pub fn notifications(token: String, limit: Option<i64>) -> JsonValue {
354
+pub fn notifications(
355
+    pooled_connection: &PooledConnection,
356
+    token: String,
357
+    limit: Option<i64>,
358
+) -> JsonValue {
291 359
     let database = database::establish_connection();
292 360
 
293 361
     match verify_token(&database, token) {
294
-        Ok(token) => match actor::get_local_actor_by_preferred_username(&database, &token.actor) {
295
-            Ok(actor) => {
296
-                match notifications_for_actor(&database, &actor, None, None, None, limit) {
297
-                    Ok(notifications) => {
298
-                        let notification_vec: Vec<Notification> = notifications
299
-                            .iter()
300
-                            .map(|notification| {
301
-                                serialize_notification_from_activitystreams(
302
-                                    &activity::get_activity_by_id(&database, *notification)
303
-                                        .unwrap(),
304
-                                )
305
-                                .unwrap()
306
-                            })
307
-                            .collect();
308
-
309
-                        return json!(notification_vec);
362
+        Ok(token) => {
363
+            match actor::get_local_actor_by_preferred_username(pooled_connection, &token.actor) {
364
+                Ok(actor) => {
365
+                    match notifications_for_actor(
366
+                        pooled_connection,
367
+                        &actor,
368
+                        None,
369
+                        None,
370
+                        None,
371
+                        limit,
372
+                    ) {
373
+                        Ok(notifications) => cached_notifications(pooled_connection, notifications),
374
+                        Err(_) => {
375
+                            json!({"error": "An error occured while generating notifications"})
376
+                        }
310 377
                     }
311
-                    Err(_) => json!({"error": "An error occured while generating notifications"}),
312 378
                 }
379
+                Err(_) => json!({"error": "User associated to token not found"}),
313 380
             }
314
-            Err(_) => json!({"error": "User associated to token not found"}),
315
-        },
381
+        }
316 382
         Err(_) => json!({"error": "Invalid oauth token"}),
317 383
     }
318 384
 }
319 385
 
320
-pub fn public_timeline(parameters: PublicTimeline) -> JsonValue {
321
-    let database = database::establish_connection();
322
-
386
+pub fn public_timeline(
387
+    pooled_connection: &PooledConnection,
388
+    parameters: PublicTimeline,
389
+) -> JsonValue {
323 390
     match get_public_timeline(
324
-        &database,
391
+        pooled_connection,
325 392
         parameters.local.unwrap_or_else(|| false),
326 393
         parameters.only_media.unwrap_or_else(|| false),
327 394
         parameters.max_id,
@@ -329,14 +396,7 @@ pub fn public_timeline(parameters: PublicTimeline) -> JsonValue {
329 396
         parameters.min_id,
330 397
         parameters.limit,
331 398
     ) {
332
-        Ok(statuses) => {
333
-            let status_vec: Vec<Status> = statuses
334
-                .iter()
335
-                .filter(|&id| status_cached_by_id(*id).is_ok())
336
-                .map(|id| status_cached_by_id(*id).unwrap())
337
-                .collect();
338
-            return json!(status_vec);
339
-        }
399
+        Ok(statuses) => cached_statuses(pooled_connection, statuses),
340 400
         Err(_e) => json!({"error": "An error occured while generating timeline."}),
341 401
     }
342 402
 }
@@ -419,116 +479,60 @@ pub fn relationships(token: &str, ids: Vec<i64>) -> JsonValue {
419 479
     }
420 480
 }
421 481
 
422
-pub fn reblog(token: String, id: i64) -> JsonValue {
423
-    let database = database::establish_connection();
424
-
425
-    match activity::get_activity_by_id(&database, id) {
426
-        Ok(activity) => match account_by_oauth_token(token) {
427
-            Ok(account) => {
428
-                kibou_api::react(
429
-                    &account.id.parse::<i64>().unwrap(),
430
-                    "Announce",
431
-                    activity.data["object"]["id"].as_str().unwrap(),
432
-                );
433
-                json!(status_cached_by_id(id))
482
+pub fn reblog(pooled_connection: &PooledConnection, token: String, id: i64) -> JsonValue {
483
+    match activity::get_activity_by_id(pooled_connection, id) {
484
+        Ok(activity) => {
485
+            let account: Result<Account, serde_json::Error> =
486
+                serde_json::from_value(account_by_oauth_token(pooled_connection, token).into());
487
+            match account {
488
+                Ok(account) => {
489
+                    kibou_api::react(
490
+                        &account.id.parse::<i64>().unwrap(),
491
+                        "Announce",
492
+                        activity.data["object"]["id"].as_str().unwrap(),
493
+                    );
494
+                    return status_by_id(pooled_connection, id);
495
+                }
496
+                Err(_) => json!({"error": "Token invalid!"}),
434 497
             }
435
-            Err(_) => json!({"error": "Token invalid!"}),
436
-        },
498
+        }
437 499
         Err(_) => json!({"error": "Status not found"}),
438 500
     }
439 501
 }
440 502
 
441
-pub fn serialize_account(mut actor: actor::Actor, include_source: bool) -> Account {
442
-    let database = database::establish_connection();
443
-
444
-    let mut new_account = Account {
445
-        id: actor.id.to_string(),
446
-        username: actor.preferred_username.clone(),
447
-        acct: actor.get_acct(),
448
-        display_name: actor.username.unwrap_or_else(|| String::from("")),
449
-        locked: false,
450
-        created_at: actor.created.to_string(),
451
-        followers_count: count_followers(&database, &actor.id),
452
-        following_count: count_followees(&database, &actor.id),
453
-        statuses_count: count_statuses(&database, &actor.actor_uri),
454
-        note: actor.summary.unwrap_or_else(|| String::from("")),
455
-        url: actor.actor_uri,
456
-        avatar: actor.icon.clone().unwrap_or_else(|| {
457
-            format!(
458
-                "{}://{}/static/assets/default_avatar.png",
459
-                env::get_value(String::from("endpoint.base_scheme")),
460
-                env::get_value(String::from("endpoint.base_domain"))
461
-            )
462
-        }),
463
-        avatar_static: actor.icon.unwrap_or_else(|| {
464
-            format!(
465
-                "{}://{}/static/assets/default_avatar.png",
466
-                env::get_value(String::from("endpoint.base_scheme")),
467
-                env::get_value(String::from("endpoint.base_domain"))
468
-            )
469
-        }),
470
-        header: format!(
471
-            "{}://{}/static/assets/default_banner.png",
472
-            env::get_value(String::from("endpoint.base_scheme")),
473
-            env::get_value(String::from("endpoint.base_domain"))
474
-        ),
475
-        header_static: format!(
476
-            "{}://{}/static/assets/default_banner.png",
477
-            env::get_value(String::from("endpoint.base_scheme")),
478
-            env::get_value(String::from("endpoint.base_domain"))
479
-        ),
480
-        emojis: vec![],
481
-        source: None,
482
-    };
483
-
484
-    if include_source {
485
-        new_account.source = Some(Source {
486
-            privacy: None,
487
-            sensitive: None,
488
-            language: None,
489
-            note: new_account.note.clone(),
490
-            fields: None,
491
-        });
492
-    }
493
-
494
-    return new_account;
495
-}
496
-
497
-pub fn serialize_status(activity: activity::Activity) -> Result<Status, ()> {
498
-    serialize_status_from_activitystreams(activity)
499
-}
500
-
501
-pub fn status_cached_by_id(id: i64) -> Result<Status, String> {
502
-    match status_by_id(id) {
503
-        Ok(status) => Ok(serde_json::from_value(status).unwrap()),
504
-        Err(e) => Err(e),
505
-    }
506
-}
503
+pub fn status_by_id(pooled_connection: &PooledConnection, id: i64) -> JsonValue {
504
+    let statuses: Vec<Status> =
505
+        serde_json::from_value(cached_statuses(pooled_connection, vec![id]).into())
506
+            .unwrap_or_else(|_| Vec::new());
507 507
 
508
-pub fn status_json_by_id(id: i64) -> JsonValue {
509
-    match status_cached_by_id(id) {
510
-        Ok(status) => json!(status),
511
-        Err(_) => json!({"error": "Status not found."}),
508
+    if statuses.len() > 0 {
509
+        return json!(statuses[0]);
510
+    } else {
511
+        return json!({"error": "Status not found"});
512 512
     }
513 513
 }
514 514
 
515
-pub fn status_post(form: StatusForm, token: String) -> JsonValue {
516
-    let database = database::establish_connection();
517
-
518
-    match verify_token(&database, token) {
519
-        Ok(token) => match actor::get_local_actor_by_preferred_username(&database, &token.actor) {
520
-            Ok(actor) => {
521
-                let status_id = kibou_api::status_build(
522
-                    actor.actor_uri,
523
-                    form.status.unwrap(),
524
-                    &form.visibility.unwrap(),
525
-                    form.in_reply_to_id,
526
-                );
527
-
528
-                return json!(status_cached_by_id(status_id));
515
+pub fn status_post(
516
+    pooled_connection: &PooledConnection,
517
+    form: StatusForm,
518
+    token: String,
519
+) -> JsonValue {
520
+    match verify_token(pooled_connection, token) {
521
+        Ok(token) => {
522
+            match actor::get_local_actor_by_preferred_username(pooled_connection, &token.actor) {
523
+                Ok(actor) => {
524
+                    let status_id = kibou_api::status_build(
525
+                        actor.actor_uri,
526
+                        form.status.unwrap(),
527
+                        &form.visibility.unwrap(),
528
+                        form.in_reply_to_id,
529
+                    );
530
+
531
+                    return status_by_id(pooled_connection, status_id);
532
+                }
533
+                Err(_) => json!({"error": "Account not found"}),
529 534
             }
530
-            Err(_) => json!({"error": "Account not found"}),
531
-        },
535
+        }
532 536
         Err(_) => json!({"error": "OAuth token invalid"}),
533 537
     }
534 538
 }
@@ -564,318 +568,27 @@ pub fn unsupported_endpoint() -> JsonValue {
564 568
     return json!([]);
565 569
 }
566 570
 
567
-cached! {
568
-    MASTODON_API_ACCOUNT_CACHE: TimedCache<(&'static str), Result<serde_json::Value, String>> = TimedCache::with_lifespan(300);
569
-fn account_by_uri(uri: &'static str) -> Result<serde_json::Value, String> = {
570
-    let database = database::establish_connection();
571
-    match actor::get_actor_by_uri(&database, uri) {
572
-        Ok(account) => {
573
-                Ok(serde_json::to_value(serialize_account(account, false)).unwrap())
574
-            },
575
-        Err(_) => Err(format!("Account not found: {}", &uri)),
576
-    }
577
-}
578
-}
579
-
580
-fn account_cached_by_uri(uri: &'static str) -> Result<Account, String> {
581
-    match account_by_uri(uri) {
582
-        Ok(account) => Ok(serde_json::from_value(account).unwrap()),
583
-        Err(e) => Err(e),
584
-    }
585
-}
586
-
587
-fn count_favourites(database: &PgConnection, status_id: &str) -> i64 {
588
-    match activity::count_ap_object_reactions_by_id(database, status_id, "Like") {
589
-        Ok(replies) => replies as i64,
590
-        Err(_) => 0,
591
-    }
592
-}
593
-
594
-fn count_followees(db_connection: &PgConnection, account_id: &i64) -> i64 {
595
-    match actor::count_followees(db_connection, *account_id) {
596
-        Ok(followees) => followees as i64,
597
-        Err(_) => 0,
598
-    }
599
-}
600
-
601
-fn count_followers(db_connection: &PgConnection, account_id: &i64) -> i64 {
602
-    match get_actor_by_id(db_connection, account_id) {
603
-        Ok(actor) => {
604
-            let activitypub_followers: Vec<serde_json::Value> =
605
-                serde_json::from_value(actor.followers["activitypub"].to_owned())
606
-                    .unwrap_or_else(|_| Vec::new());
607
-            return activitypub_followers.len() as i64;
608
-        }
609
-        Err(_) => 0,
610
-    }
611
-}
612
-
613
-fn count_reblogs(database: &PgConnection, status_id: &str) -> i64 {
614
-    match activity::count_ap_object_reactions_by_id(database, status_id, "Announce") {
615
-        Ok(replies) => replies as i64,
616
-        Err(_) => 0,
617
-    }
618
-}
619
-
620
-fn count_replies(database: &PgConnection, status_id: &str) -> i64 {
621
-    match activity::count_ap_object_replies_by_id(database, status_id) {
622
-        Ok(replies) => replies as i64,
623
-        Err(_) => 0,
624
-    }
625
-}
626
-
627
-fn count_statuses(db_connection: &PgConnection, account_uri: &str) -> i64 {
628
-    match activity::count_ap_notes_for_actor(db_connection, account_uri) {
629
-        Ok(statuses) => statuses as i64,
630
-        Err(_) => 0,
631
-    }
632
-}
633
-
634
-fn serialize_attachments_from_activitystreams(activity: &activity::Activity) -> Vec<Attachment> {
635
-    let mut media_attachments: Vec<Attachment> = Vec::new();
636
-    match activity.data["object"].get("attachment") {
637
-        Some(_attachments) => {
638
-            let serialized_attachments: Vec<activitypub::Attachment> =
639
-                serde_json::from_value(activity.data["object"]["attachment"].to_owned()).unwrap();
640
-
641
-            for attachment in serialized_attachments {
642
-                media_attachments.push(Attachment {
643
-                    id: attachment
644
-                        .name
645
-                        .unwrap_or_else(|| String::from("Unnamed attachment")),
646
-                    _type: String::from("image"),
647
-                    url: attachment.url.clone(),
648
-                    remote_url: Some(attachment.url.clone()),
649
-                    preview_url: attachment.url,
650
-                    text_url: None,
651
-                    meta: None,
652
-                    description: attachment.content,
653
-                });
654
-            }
655
-        }
656
-        None => (),
657
-    }
658
-    return media_attachments;
659
-}
660
-
661
-fn serialize_notification_from_activitystreams(
662
-    activity: &activity::Activity,
663
-) -> Result<Notification, ()> {
664
-    let database = database::establish_connection();
665
-    let serialized_activity: activitypub::activity::Activity =
666
-        serde_json::from_value(activity.data.to_owned()).unwrap();
667
-
668
-    match serialized_activity._type.as_str() {
669
-        "Follow" => Ok(Notification {
670
-            id: activity.id.to_string(),
671
-            _type: String::from("follow"),
672
-            created_at: serialized_activity.published,
673
-            account: account_cached_by_uri(Box::leak(activity.actor.to_owned().into_boxed_str()))
674
-                .unwrap(),
675
-            status: None,
676
-        }),
677
-        "Create" => Ok(Notification {
678
-            id: activity.id.to_string(),
679
-            _type: String::from("mention"),
680
-            created_at: serialized_activity.published,
681
-            account: account_cached_by_uri(Box::leak(activity.actor.to_owned().into_boxed_str()))
682
-                .unwrap(),
683
-            status: Some(status_cached_by_id(activity.id).unwrap()),
684
-        }),
685
-        "Announce" => Ok(Notification {
686
-            id: activity.id.to_string(),
687
-            _type: String::from("reblog"),
688
-            created_at: serialized_activity.published,
689
-            account: account_cached_by_uri(Box::leak(activity.actor.to_owned().into_boxed_str()))
690
-                .unwrap(),
691
-            status: Some(
692
-                status_cached_by_id(
693
-                    get_ap_object_by_id(&database, serialized_activity.object.as_str().unwrap())
694
-                        .unwrap()
695
-                        .id,
696
-                )
697
-                .unwrap(),
698
-            ),
699
-        }),
700
-        "Like" => Ok(Notification {
701
-            id: activity.id.to_string(),
702
-            _type: String::from("favourite"),
703
-            created_at: serialized_activity.published,
704
-            account: account_cached_by_uri(Box::leak(activity.actor.to_owned().into_boxed_str()))
705
-                .unwrap(),
706
-            status: Some(
707
-                status_cached_by_id(
708
-                    get_ap_object_by_id(&database, serialized_activity.object.as_str().unwrap())
709
-                        .unwrap()
710
-                        .id,
711
-                )
712
-                .unwrap(),
713
-            ),
714
-        }),
715
-        _ => Err(()),
716
-    }
717
-}
718
-
719
-fn serialize_status_from_activitystreams(activity: activity::Activity) -> Result<Status, ()> {
720
-    let database = database::establish_connection();
721
-    let serialized_attachments: Vec<Attachment> =
722
-        serialize_attachments_from_activitystreams(&activity);
723
-    let serialized_activity: activitypub::activity::Activity =
724
-        serde_json::from_value(activity.data).unwrap();
725
-    let serialized_account =
726
-        account_cached_by_uri(Box::leak(activity.actor.clone().into_boxed_str())).unwrap();
727
-
728
-    match serialized_activity._type.as_str() {
729
-        "Create" => {
730
-            let serialized_object: activitypub::activity::Object =
731
-                serde_json::from_value(serialized_activity.object).unwrap();
732
-            let mut parent_object: Option<String>;
733
-            let mut parent_object_account: Option<String>;
734
-
735
-            match serialized_object.inReplyTo {
736
-                Some(object) => match get_ap_object_by_id(&database, &object) {
737
-                    Ok(parent_activity) => {
738
-                        parent_object = Some(parent_activity.id.to_string());
739
-
740
-                        match get_actor_by_uri(&database, &parent_activity.actor) {
741
-                            Ok(parent_actor) => {
742
-                                parent_object_account = Some(parent_actor.id.to_string())
743
-                            }
744
-                            Err(_) => parent_object_account = None,
745
-                        }
746
-                    }
747
-                    Err(_) => {
748
-                        parent_object = None;
749
-                        parent_object_account = None;
750
-                    }
751
-                },
752
-                None => {
753
-                    parent_object = None;
754
-                    parent_object_account = None;
755
-                }
756
-            }
757
-
758
-            Ok(Status {
759
-                id: activity.id.to_string(),
760
-                uri: serialized_object.id.clone(),
761
-                url: Some(serialized_object.id.clone()),
762
-                account: serialized_account,
763
-                in_reply_to_id: parent_object,
764
-                in_reply_to_account_id: parent_object_account,
765
-                reblog: None,
766
-                content: serialized_object.content,
767
-                created_at: serialized_object.published,
768
-                emojis: vec![],
769
-                replies_count: count_replies(&database, &serialized_object.id),
770
-                reblogs_count: count_reblogs(&database, &serialized_object.id),
771
-                favourites_count: count_favourites(&database, &serialized_object.id),
772
-                reblogged: Some(
773
-                    type_exists_for_object_id(
774
-                        &database,
775
-                        "Announce",
776
-                        &activity.actor,
777
-                        &serialized_object.id,
778
-                    )
779
-                    .unwrap_or_else(|_| false),
780
-                ),
781
-                favourited: Some(
782
-                    type_exists_for_object_id(
783
-                        &database,
784
-                        "Like",
785
-                        &activity.actor,
786
-                        &serialized_object.id,
787
-                    )
788
-                    .unwrap_or_else(|_| false),
789
-                ),
790
-                muted: Some(false),
791
-                sensitive: serialized_object.sensitive.unwrap_or_else(|| false),
792
-                spoiler_text: String::new(),
793
-                visibility: String::from("public"),
794
-                media_attachments: serialized_attachments,
795
-                mentions: vec![],
796
-                tags: vec![],
797
-                application: serde_json::json!({"name": "Web", "website": null}),
798
-                language: None,
799
-                pinned: None,
800
-            })
801
-        }
802
-        "Announce" => {
803
-            match get_ap_object_by_id(&database, serialized_activity.object.as_str().unwrap()) {
804
-                Ok(reblog) => {
805
-                    let serialized_reblog: Status =
806
-                        serialize_status_from_activitystreams(reblog).unwrap();
807
-
808
-                    Ok(Status {
809
-                        id: activity.id.to_string(),
810
-                        uri: serialized_activity.id.clone(),
811
-                        url: Some(serialized_activity.id.clone()),
812
-                        account: serialized_account,
813
-                        in_reply_to_id: None,
814
-                        in_reply_to_account_id: None,
815
-                        reblog: Some(serde_json::to_value(serialized_reblog).unwrap()),
816
-                        content: String::from("reblog"),
817
-                        created_at: serialized_activity.published,
818
-                        emojis: vec![],
819
-                        replies_count: 0,
820
-                        reblogs_count: 0,
821
-                        favourites_count: 0,
822
-                        reblogged: Some(false),
823
-                        favourited: Some(false),
824
-                        muted: Some(false),
825
-                        sensitive: false,
826
-                        spoiler_text: String::new(),
827
-                        visibility: String::from("public"),
828
-                        media_attachments: vec![],
829
-                        mentions: vec![],
830
-                        tags: vec![],
831
-                        application: serde_json::json!({"name": "Web", "website": null}),
832
-                        language: None,
833
-                        pinned: None,
834
-                    })
835
-                }
836
-                Err(_e) => Err(()),
837
-            }
838
-        }
839
-        _ => Err(()),
840
-    }
841
-}
842
-
843
-cached! {
844
-    MASTODON_API_STATUS_CACHE: TimedCache<(i64), Result<serde_json::Value, String>> = TimedCache::with_lifespan(300);
845
-fn status_by_id(id: i64) -> Result<serde_json::Value, String> = {
846
-    let database = database::establish_connection();
847
-    match activity::get_activity_by_id(&database, id) {
848
-        Ok(activity) => {
849
-            match serialize_status(activity)
850
-            {
851
-                Ok(serialized_status) => Ok(serde_json::to_value(serialized_status).unwrap()),
852
-                Err(_) => Err(format!("Failed to serialize status:"))
853
-            }
854
-            },
855
-        Err(_) => Err(format!("Status not found: {}", &id)),
856
-    }
857
-}
858
-}
859
-
860 571
 fn status_children_for_id(
861
-    db_connection: &PgConnection,
572
+    pooled_connection: &PooledConnection,
862 573
     id: i64,
863 574
     resolve_children: bool,
864 575
 ) -> Vec<Status> {
865 576
     let mut statuses: Vec<Status> = vec![];
866 577
 
867
-    match status_cached_by_id(id) {
868
-        Ok(status) => match get_ap_object_replies_by_id(&db_connection, &status.uri) {
578
+    let head_status: Result<Status, serde_json::Error> =
579
+        serde_json::from_value(status_by_id(pooled_connection, id).into());
580
+    match head_status {
581
+        Ok(status) => match get_ap_object_replies_by_id(pooled_connection, &status.uri) {
869 582
             Ok(replies) => {
870 583
                 if !replies.is_empty() {
871 584
                     for reply in replies {
872 585
                         if resolve_children {
873 586
                             let mut child_statuses =
874
-                                status_children_for_id(&db_connection, reply.id, true);
587
+                                status_children_for_id(pooled_connection, reply.id, true);
875 588
                             statuses.append(&mut child_statuses);
876 589
                         }
877 590
 
878
-                        statuses.push(serialize_status(reply).unwrap());
591
+                        statuses.push(Status::try_from(reply).unwrap());
879 592
                     }
880 593
                 }
881 594
             }
@@ -886,14 +599,20 @@ fn status_children_for_id(
886 599
     return statuses;
887 600
 }
888 601
 
889
-fn status_parents_for_id(db_connection: &PgConnection, id: i64, is_head: bool) -> Vec<Status> {
602
+fn status_parents_for_id(
603
+    pooled_connection: &PooledConnection,
604
+    id: i64,
605
+    is_head: bool,
606
+) -> Vec<Status> {
890 607
     let mut statuses: Vec<Status> = vec![];
891 608
 
892
-    match status_cached_by_id(id) {
609
+    let head_status: Result<Status, serde_json::Error> =
610
+        serde_json::from_value(status_by_id(pooled_connection, id).into());
611
+    match head_status {
893 612
         Ok(status) => {
894 613
             if status.in_reply_to_id.is_some() {
895 614
                 statuses.append(&mut status_parents_for_id(
896
-                    &db_connection,
615
+                    pooled_connection,
897 616
                     status
898 617
                         .in_reply_to_id
899 618
                         .clone()
@@ -905,7 +624,7 @@ fn status_parents_for_id(db_connection: &PgConnection, id: i64, is_head: bool) -
905 624
             }
906 625
 
907 626
             if !is_head {
908
-                statuses.append(&mut status_children_for_id(db_connection, id, false));
627
+                statuses.append(&mut status_children_for_id(pooled_connection, id, false));
909 628
                 statuses.dedup_by_key(|ref mut s| s.id == status.id);
910 629
                 statuses.push(status);
911 630
             }

+ 356
- 0
src/mastodon_api/mod.rs View File

@@ -1,11 +1,22 @@
1 1
 pub mod controller;
2 2
 pub mod routes;
3
+use activity::{
4
+    count_ap_notes_for_actor, count_ap_object_reactions_by_id, count_ap_object_replies_by_id,
5
+    get_ap_object_by_id, Activity,
6
+};
7
+use activitypub;
8
+use actor::{count_followees, get_actor_by_uri, Actor};
9
+use database;
10
+use database::PooledConnection;
11
+use env;
12
+use lru::LruCache;
3 13
 use rocket::request;
4 14
 use rocket::request::FromRequest;
5 15
 use rocket::request::Request;
6 16
 use rocket::Outcome;
7 17
 use serde::{Deserialize, Serialize};
8 18
 use serde_json;
19
+use std::sync::{Arc, Mutex};
9 20
 
10 21
 #[derive(Serialize, Deserialize)]
11 22
 pub struct Account {
@@ -200,6 +211,342 @@ pub struct Tag {
200 211
     pub history: Option<serde_json::Value>,
201 212
 }
202 213
 
214
+impl Account {
215
+    pub fn from_actor(
216
+        pooled_connection: &PooledConnection,
217
+        mut actor: Actor,
218
+        include_source: bool,
219
+    ) -> Account {
220
+        let followees = count_followees(&pooled_connection, actor.id).unwrap_or_else(|_| 0) as i64;
221
+        let followers: Vec<serde_json::Value> =
222
+            serde_json::from_value(actor.followers["activitypub"].to_owned())
223
+                .unwrap_or_else(|_| Vec::new());
224
+
225
+        let statuses = count_ap_notes_for_actor(&pooled_connection, &actor.actor_uri)
226
+            .unwrap_or_else(|_| 0) as i64;
227
+
228
+        let mut new_account = Account {
229
+            id: actor.id.to_string(),
230
+            username: actor.preferred_username.clone(),
231
+            acct: actor.get_acct(),
232
+            display_name: actor.username.unwrap_or_else(|| String::from("")),
233
+            locked: false,
234
+            created_at: actor.created.to_string(),
235
+            followers_count: followers.len() as i64,
236
+            following_count: followees,
237
+            statuses_count: statuses,
238
+            note: actor.summary.unwrap_or_else(|| String::from("")),
239
+            url: actor.actor_uri,
240
+            avatar: actor.icon.clone().unwrap_or_else(|| {
241
+                format!(
242
+                    "{}://{}/static/assets/default_avatar.png",
243
+                    env::get_value(String::from("endpoint.base_scheme")),
244
+                    env::get_value(String::from("endpoint.base_domain"))
245
+                )
246
+            }),
247
+            avatar_static: actor.icon.unwrap_or_else(|| {
248
+                format!(
249
+                    "{}://{}/static/assets/default_avatar.png",
250
+                    env::get_value(String::from("endpoint.base_scheme")),
251
+                    env::get_value(String::from("endpoint.base_domain"))
252
+                )
253
+            }),
254
+            header: format!(
255
+                "{}://{}/static/assets/default_banner.png",
256
+                env::get_value(String::from("endpoint.base_scheme")),
257
+                env::get_value(String::from("endpoint.base_domain"))
258
+            ),
259
+            header_static: format!(
260
+                "{}://{}/static/assets/default_banner.png",
261
+                env::get_value(String::from("endpoint.base_scheme")),
262
+                env::get_value(String::from("endpoint.base_domain"))
263
+            ),
264
+            emojis: vec![],
265
+            source: None,
266
+        };
267
+
268
+        if include_source {
269
+            new_account.source = Some(Source {
270
+                privacy: None,
271
+                sensitive: None,
272
+                language: None,
273
+                note: new_account.note.clone(),
274
+                fields: None,
275
+            });
276
+        }
277
+
278
+        return new_account;
279
+    }
280
+}
281
+
282
+impl Notification {
283
+    pub fn try_from(activity: Activity) -> Result<Self, ()> {
284
+        let activitypub_activity: Result<activitypub::activity::Activity, serde_json::Error> =
285
+            serde_json::from_value(activity.data);
286
+        let pooled_connection = &PooledConnection(database::POOL.get().unwrap());
287
+
288
+        if activitypub_activity.is_ok() {
289
+            return Notification::from_activitystreams(
290
+                pooled_connection,
291
+                activity.id,
292
+                activitypub_activity.unwrap(),
293
+            );
294
+        } else {
295
+            return Err(());
296
+        }
297
+    }
298
+
299
+    fn from_activitystreams(
300
+        pooled_connection: &PooledConnection,
301
+        activity_id: i64,
302
+        activity: activitypub::activity::Activity,
303
+    ) -> Result<Self, ()> {
304
+        let account = serde_json::from_value(
305
+            controller::cached_account(
306
+                pooled_connection,
307
+                Box::leak(activity.actor.to_owned().into_boxed_str()),
308
+            )
309
+            .into(),
310
+        )
311
+        .unwrap();
312
+        let notification_type = match activity._type.as_str() {
313
+            "Follow" => String::from("follow"),
314
+            "Create" => String::from("mention"),
315
+            "Announce" => String::from("reblog"),
316
+            "Like" => String::from("favourite"),
317
+            _ => String::from(""),
318
+        };
319
+        let mut status: Option<Status> = None;
320
+
321
+        if !notification_type.is_empty() {
322
+            if &notification_type == "reblog" || &notification_type == "favourite" {
323
+                status = serde_json::from_value(
324
+                    controller::status_by_id(
325
+                        pooled_connection,
326
+                        get_ap_object_by_id(pooled_connection, activity.object.as_str().unwrap())
327
+                            .unwrap()
328
+                            .id,
329
+                    )
330
+                    .into(),
331
+                )
332
+                .unwrap_or_else(|_| None);
333
+            } else if &notification_type == "mention" {
334
+                status = serde_json::from_value(
335
+                    controller::status_by_id(pooled_connection, activity_id).into(),
336
+                )
337
+                .unwrap();
338
+            }
339
+
340
+            return Ok(Notification {
341
+                id: activity_id.to_string(),
342
+                _type: notification_type,
343
+                created_at: activity.published,
344
+                account: account,
345
+                status: status,
346
+            });
347
+        } else {
348
+            return Err(());
349
+        }
350
+    }
351
+}
352
+
353
+impl Status {
354
+    pub fn try_from(activity: Activity) -> Result<Self, ()> {
355
+        let activitypub_activity: Result<activitypub::activity::Activity, serde_json::Error> =
356
+            serde_json::from_value(activity.data);
357
+        let pooled_connection = &PooledConnection(database::POOL.get().unwrap());
358
+
359
+        if activitypub_activity.is_ok() {
360
+            return Status::from_activitystreams(
361
+                pooled_connection,
362
+                activity.id,
363
+                activitypub_activity.unwrap(),
364
+            );
365
+        } else {
366
+            return Err(());
367
+        }
368
+    }
369
+    fn from_activitystreams(
370
+        pooled_connection: &PooledConnection,
371
+        activity_id: i64,
372
+        activity: activitypub::activity::Activity,
373
+    ) -> Result<Self, ()> {
374
+        let account: Account = serde_json::from_value(
375
+            controller::cached_account(pooled_connection, &activity.actor).into(),
376
+        )
377
+        .unwrap();
378
+
379
+        let mut mentions: Vec<Mention> = Vec::new();
380
+        for actor in &activity.to {
381
+            let mention_account: Result<Account, serde_json::Error> = serde_json::from_value(
382
+                controller::cached_account(pooled_connection, &actor).into(),
383
+            );
384
+            // Just unwrapping every account would mean that an entire status fails serializing,
385
+            // because of one invalid account.
386
+            match mention_account {
387
+                Ok(mention_account) => mentions.push(Mention {
388
+                    url: mention_account.url,
389
+                    username: mention_account.username,
390
+                    acct: mention_account.acct,
391
+                    id: mention_account.id,
392
+                }),
393
+                Err(_) => (),
394
+            };
395
+        }
396
+
397
+        // The 'Public' and 'Unlisted' scope can be easily determined by the existence of
398
+        // `https://www.w3.org/ns/activitystreams#Public` in either the 'to' or 'cc' field.
399
+        //
400
+        // Note that different formats like `as:Public` have already been normalized to
401
+        // `https://www.w3.org/ns/activitystreams#Public` in activitypub::validator.
402
+        let visibility = if activity
403
+            .to
404
+            .contains(&"https://www.w3.org/ns/activitystreams#Public".to_string())
405
+        {
406
+            String::from("public")
407
+        } else if activity
408
+            .cc
409
+            .contains(&"https://www.w3.org/ns/activitystreams#Public".to_string())
410
+        {
411
+            String::from("unlisted")
412
+        // XX - This might cause issues, as the 'Followers' endpoint of remote actors might differ
413
+        // from Kibou's schema. But as of now Kibou does not keep track of that endpoint.
414
+        } else if activity.to.contains(&format!("{}/followers", account.url)) {
415
+            String::from("private")
416
+        } else {
417
+            String::from("direct")
418
+        };
419
+
420
+        match activity._type.as_str() {
421
+            "Create" => {
422
+                let inner_object: activitypub::activity::Object =
423
+                    serde_json::from_value(activity.object.clone()).unwrap();
424
+                let mut in_reply_to: Option<String> = None;
425
+                let mut in_reply_to_account: Option<String> = None;
426
+                if inner_object.inReplyTo.is_some() {
427
+                    in_reply_to = match get_ap_object_by_id(
428
+                        pooled_connection,
429
+                        &inner_object.inReplyTo.unwrap(),
430
+                    ) {
431
+                        Ok(parent_activity) => {
432
+                            in_reply_to_account = match get_actor_by_uri(
433
+                                pooled_connection,
434
+                                &parent_activity.data["actor"].as_str().unwrap(),
435
+                            ) {
436
+                                Ok(parent_actor) => Some(parent_actor.id.to_string()),
437
+                                Err(_) => None,
438
+                            };
439
+                            Some(parent_activity.id.to_string())
440
+                        }
441
+                        Err(_) => None,
442
+                    };
443
+                }
444
+
445
+                let mut media_attachments: Vec<Attachment> = Vec::new();
446
+                match activity.object.get("attachment") {
447
+                    Some(attachments) => {
448
+                        let attachments: Vec<activitypub::Attachment> =
449
+                            serde_json::from_value(activity.object["attachment"].to_owned())
450
+                                .unwrap();
451
+
452
+                        for attachment in attachments {
453
+                            media_attachments.push(Attachment {
454
+                                id: attachment
455
+                                    .name
456
+                                    .unwrap_or_else(|| String::from("Unnamed attachment")),
457
+                                _type: String::from("image"),
458
+                                url: attachment.url.clone(),
459
+                                remote_url: Some(attachment.url.clone()),
460
+                                preview_url: attachment.url,
461
+                                text_url: None,
462
+                                meta: None,
463
+                                description: attachment.content,
464
+                            });
465
+                        }
466
+                    }
467
+                    None => (),
468
+                }
469
+
470
+                let favourites =
471
+                    count_ap_object_reactions_by_id(pooled_connection, &inner_object.id, "Like")
472
+                        .unwrap_or_else(|_| 0) as i64;
473
+                let reblogs = count_ap_object_reactions_by_id(
474
+                    pooled_connection,
475
+                    &inner_object.id,
476
+                    "Announce",
477
+                )
478
+                .unwrap_or_else(|_| 0) as i64;
479
+                let replies = count_ap_object_replies_by_id(pooled_connection, &inner_object.id)
480
+                    .unwrap_or_else(|_| 0) as i64;
481
+                return Ok(Status {
482
+                    id: activity_id.to_string(),
483
+                    uri: inner_object.id.clone(),
484
+                    url: Some(inner_object.id.clone()),
485
+                    account: account,
486
+                    in_reply_to_id: in_reply_to,
487
+                    in_reply_to_account_id: in_reply_to_account,
488
+                    reblog: None,
489
+                    content: inner_object.content,
490
+                    created_at: inner_object.published,
491
+                    emojis: vec![],
492
+                    replies_count: replies,
493
+                    reblogs_count: reblogs,
494
+                    favourites_count: favourites,
495
+                    reblogged: Some(false),
496
+                    favourited: Some(false),
497
+                    muted: None,
498
+                    sensitive: inner_object.sensitive.unwrap_or_else(|| false),
499
+                    spoiler_text: "".to_string(),
500
+                    visibility: visibility,
501
+                    media_attachments: media_attachments,
502
+                    mentions: mentions,
503
+                    tags: vec![],
504
+                    application: serde_json::json!({"name": "Web", "website": null}),
505
+                    language: None,
506
+                    pinned: None,
507
+                });
508
+            }
509
+            "Announce" => {
510
+                match get_ap_object_by_id(&pooled_connection, activity.object.as_str().unwrap()) {
511
+                    Ok(reblog) => {
512
+                        let serialized_reblog: Status = Status::try_from(reblog).unwrap();
513
+
514
+                        Ok(Status {
515
+                            id: activity_id.to_string(),
516
+                            uri: activity.id.clone(),
517
+                            url: Some(activity.id.clone()),
518
+                            account: account,
519
+                            in_reply_to_id: None,
520
+                            in_reply_to_account_id: None,
521
+                            reblog: Some(serde_json::to_value(serialized_reblog).unwrap()),
522
+                            content: String::from("reblog"),
523
+                            created_at: activity.published,
524
+                            emojis: vec![],
525
+                            replies_count: 0,
526
+                            reblogs_count: 0,
527
+                            favourites_count: 0,
528
+                            reblogged: Some(false),
529
+                            favourited: Some(false),
530
+                            muted: Some(false),
531
+                            sensitive: false,
532
+                            spoiler_text: String::new(),
533
+                            visibility: visibility,
534
+                            media_attachments: vec![],
535
+                            mentions: vec![],
536
+                            tags: vec![],
537
+                            application: serde_json::json!({"name": "Web", "website": null}),
538
+                            language: None,
539
+                            pinned: None,
540
+                        })
541
+                    }
542
+                    Err(_) => Err(()),
543
+                }
544
+            }
545
+            _ => return Err(()),
546
+        }
547
+    }