Compare commits
352
Commits
SOGo-2.3.2
...
SOGo-2.3.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30b08941f6 | ||
|
|
e5b42c5e98 | ||
|
|
ed5d922ea4 | ||
|
|
82b919e7ee | ||
|
|
821b7c19ce | ||
|
|
bed5175a74 | ||
|
|
188328d5d7 | ||
|
|
17afe7c077 | ||
|
|
bfb7fa2e14 | ||
|
|
e8ff3dc539 | ||
|
|
20b1d3c0e9 | ||
|
|
3a66e54bd0 | ||
|
|
d1c3327d47 | ||
|
|
100841efbd | ||
|
|
8617ddc05e | ||
|
|
8e94c33a0f | ||
|
|
32bb1456e2 | ||
|
|
d01f5d36fa | ||
|
|
6de15edb9c | ||
|
|
a2078da5f0 | ||
|
|
2193dbb678 | ||
|
|
cb9f26cc1f | ||
|
|
e4473a656b | ||
|
|
7cbf019742 | ||
|
|
456ff7d5a3 | ||
|
|
5f0061205f | ||
|
|
11a2bcfffd | ||
|
|
0b1fbf0dee | ||
|
|
499dbf68ae | ||
|
|
4c6c0a3644 | ||
|
|
1c65424242 | ||
|
|
08bf6d528b | ||
|
|
a9083004ef | ||
|
|
ec02c41d9b | ||
|
|
07e3c48ed0 | ||
|
|
920bfba8b8 | ||
|
|
bc2e46cda7 | ||
|
|
e9d24fe0e6 | ||
|
|
24d32ae220 | ||
|
|
8baf8731b7 | ||
|
|
b9cdc437eb | ||
|
|
a451a1120b | ||
|
|
6cfed4eb67 | ||
|
|
a55c98b98d | ||
|
|
7c0649efcf | ||
|
|
0ca6512617 | ||
|
|
3ee29c0b71 | ||
|
|
7b4c6346f8 | ||
|
|
d5fd719212 | ||
|
|
be7971ceef | ||
|
|
3f0c2b647c | ||
|
|
155acfa9a6 | ||
|
|
57901b8469 | ||
|
|
5ba476789c | ||
|
|
49d5a72d65 | ||
|
|
f34aa2996d | ||
|
|
5686e086cd | ||
|
|
ed9ec04669 | ||
|
|
737d2ffccc | ||
|
|
7ca66b4078 | ||
|
|
04c68e0674 | ||
|
|
bf7f5e8be7 | ||
|
|
f1d0f770df | ||
|
|
596c22d828 | ||
|
|
e223d064d9 | ||
|
|
dbf6426449 | ||
|
|
7fc6eec157 | ||
|
|
f48b3405d7 | ||
|
|
52bc589ab8 | ||
|
|
03f879433b | ||
|
|
ad141295ed | ||
|
|
5fef815191 | ||
|
|
ec28fdc8d0 | ||
|
|
b1590f6b2e | ||
|
|
800d143a35 | ||
|
|
a503b34d7a | ||
|
|
65670027cb | ||
|
|
d5bf661cf1 | ||
|
|
dc2878b5e2 | ||
|
|
f9aab0de75 | ||
|
|
5c58299458 | ||
|
|
57fad470fb | ||
|
|
e0b7efbbbf | ||
|
|
4278e417a7 | ||
|
|
230688bf4e | ||
|
|
1a1f74b5d0 | ||
|
|
93d1c3d4e7 | ||
|
|
ea37a9cd29 | ||
|
|
8e8e19666a | ||
|
|
76b4563b2f | ||
|
|
4ce3947e60 | ||
|
|
0a8f52ce7d | ||
|
|
9bb2473c8e | ||
|
|
2d5995a8f4 | ||
|
|
cee83df085 | ||
|
|
0c5f4edb36 | ||
|
|
90378b76eb | ||
|
|
6aeb13ba65 | ||
|
|
216371fd80 | ||
|
|
5927d3303c | ||
|
|
6a3e64f849 | ||
|
|
490043b2e1 | ||
|
|
210d6908b8 | ||
|
|
ca7f093155 | ||
|
|
2ad00f8cef | ||
|
|
b5780357cb | ||
|
|
67f38e1127 | ||
|
|
36985a19e9 | ||
|
|
06cbe527e8 | ||
|
|
51dbfaba15 | ||
|
|
3d55396661 | ||
|
|
2c97b680a8 | ||
|
|
f9cb896f92 | ||
|
|
46edcce1d5 | ||
|
|
1b14fee96e | ||
|
|
0777e9fe67 | ||
|
|
8b078817e2 | ||
|
|
dd56d5e85c | ||
|
|
e009af6864 | ||
|
|
673db179b1 | ||
|
|
fd0f1499b1 | ||
|
|
20575226b8 | ||
|
|
8b04a468c2 | ||
|
|
ccd9e385a0 | ||
|
|
376e717f45 | ||
|
|
5b25f85c02 | ||
|
|
63b8c3ed5e | ||
|
|
c76c48fb0c | ||
|
|
7fe1b5f046 | ||
|
|
2a25675b3a | ||
|
|
dbfd86db04 | ||
|
|
135535e00e | ||
|
|
e386404b0a | ||
|
|
966d13c5b6 | ||
|
|
4ae5feb131 | ||
|
|
db213c2b73 | ||
|
|
c2922e2a26 | ||
|
|
1529e7ec4e | ||
|
|
332508e2db | ||
|
|
d2ea6fef2e | ||
|
|
75ff4af7cc | ||
|
|
916c04387b | ||
|
|
7c460b694e | ||
|
|
29f2d960a7 | ||
|
|
225fe847a7 | ||
|
|
720b1d5ab5 | ||
|
|
28bd4ca318 | ||
|
|
c6dbcb2f7c | ||
|
|
5dfbfe827d | ||
|
|
6ddb40fd2b | ||
|
|
37f29c06ea | ||
|
|
248892c394 | ||
|
|
b4e4b9c29f | ||
|
|
e94b4416f8 | ||
|
|
8d19ec4aff | ||
|
|
aeeeeea802 | ||
|
|
41e388c5f6 | ||
|
|
db17872dd6 | ||
|
|
73e86bb8e5 | ||
|
|
c3bce410f1 | ||
|
|
980db7a980 | ||
|
|
c5c6ff33d8 | ||
|
|
7e1bbe34dc | ||
|
|
a618386d5e | ||
|
|
5993fe97c3 | ||
|
|
7cdf48335b | ||
|
|
3dff73636c | ||
|
|
2fc21e48d8 | ||
|
|
98ed9c3b17 | ||
|
|
f9884e3f55 | ||
|
|
10cabca57e | ||
|
|
2e1ed274c8 | ||
|
|
e4cd451be8 | ||
|
|
603da75818 | ||
|
|
9ed3812795 | ||
|
|
a0b15d0c8c | ||
|
|
7e4571874e | ||
|
|
b076801e1b | ||
|
|
0316e441fc | ||
|
|
75208428dd | ||
|
|
942f48c9a0 | ||
|
|
24a7515bb5 | ||
|
|
a8e5a3c025 | ||
|
|
0b69727a49 | ||
|
|
2a2d141629 | ||
|
|
b98d112738 | ||
|
|
1c8b693656 | ||
|
|
15ab0f851d | ||
|
|
1f98d7cced | ||
|
|
2755a83e65 | ||
|
|
9936477f07 | ||
|
|
2e1bfb25d5 | ||
|
|
25dbce42cd | ||
|
|
a9fe80c367 | ||
|
|
bc5aa3cacb | ||
|
|
0ddbd79fad | ||
|
|
2a3367a13b | ||
|
|
4b7cbba0c8 | ||
|
|
2deda01e5c | ||
|
|
8c0ef9cf50 | ||
|
|
8456d49686 | ||
|
|
5a3cafbbb0 | ||
|
|
dc085789e7 | ||
|
|
f6973d8aa4 | ||
|
|
94d4da6e1e | ||
|
|
2f63542e07 | ||
|
|
3155bd0172 | ||
|
|
2518b37e32 | ||
|
|
8e3e4a5445 | ||
|
|
01dcf3d6c8 | ||
|
|
fa4d3a0e20 | ||
|
|
bbdb27e75e | ||
|
|
1279629e86 | ||
|
|
f7d748ea98 | ||
|
|
2d1a4b320c | ||
|
|
e8e610c7b3 | ||
|
|
8df311547f | ||
|
|
f42d6f5f4a | ||
|
|
01642eca3e | ||
|
|
9554e92c8b | ||
|
|
2ae8c6f8b2 | ||
|
|
f64b324133 | ||
|
|
31deae11b4 | ||
|
|
19676593ea | ||
|
|
4a27beabdd | ||
|
|
8dae5faaa1 | ||
|
|
85ce41dc68 | ||
|
|
0622980f38 | ||
|
|
a73df96b78 | ||
|
|
7015713741 | ||
|
|
7644d6fb4d | ||
|
|
9dcf459827 | ||
|
|
4c7bd6dd76 | ||
|
|
ec598321c4 | ||
|
|
4b8a9bcc3b | ||
|
|
a9984d9487 | ||
|
|
88a74bcb5c | ||
|
|
2a90102a81 | ||
|
|
fbc73dc3bc | ||
|
|
1d78b2df96 | ||
|
|
6aef713d28 | ||
|
|
71db3945cb | ||
|
|
da230410be | ||
|
|
82114f75aa | ||
|
|
f8d31a6413 | ||
|
|
707aabde15 | ||
|
|
ceec497aec | ||
|
|
46f93d8629 | ||
|
|
f2347e13d7 | ||
|
|
106f8ec314 | ||
|
|
f58554a700 | ||
|
|
76b8de1805 | ||
|
|
0751c51b89 | ||
|
|
64456c10e5 | ||
|
|
5f5b5d9273 | ||
|
|
194c415631 | ||
|
|
08f05ac2ef | ||
|
|
af413f2b32 | ||
|
|
77bba8e5ea | ||
|
|
946665ff49 | ||
|
|
ef7de7c10d | ||
|
|
106c6a24ae | ||
|
|
863f182e84 | ||
|
|
ba37353e42 | ||
|
|
1915748e33 | ||
|
|
b170ae866f | ||
|
|
7c69e37449 | ||
|
|
7f26d47cd1 | ||
|
|
b55ec52bfe | ||
|
|
f919df050a | ||
|
|
c2e4f7441d | ||
|
|
ca27464984 | ||
|
|
6cbc5573a1 | ||
|
|
e4fae417b9 | ||
|
|
a6bbe13f9b | ||
|
|
8cfb0f0d44 | ||
|
|
08f57f6ad5 | ||
|
|
30a94161af | ||
|
|
ec533809aa | ||
|
|
0d05e3579c | ||
|
|
bf4a581d0f | ||
|
|
9d3f3c619d | ||
|
|
7d49674d9d | ||
|
|
48832a4878 | ||
|
|
eda84c7b5d | ||
|
|
9d31023724 | ||
|
|
41e80bb105 | ||
|
|
e428e74e6c | ||
|
|
7a5e550510 | ||
|
|
0605fdf02a | ||
|
|
d3290dbb8d | ||
|
|
34f626b84c | ||
|
|
e173c6d621 | ||
|
|
1425bb1c75 | ||
|
|
5b65eaa572 | ||
|
|
892bd693d7 | ||
|
|
6812cb08dc | ||
|
|
b62167dd00 | ||
|
|
e0d49d2407 | ||
|
|
c8d05d41a5 | ||
|
|
1013c473cd | ||
|
|
b8d77259cd | ||
|
|
40cb81fd0b | ||
|
|
2937ba7f30 | ||
|
|
3c6fb10d9f | ||
|
|
b378d258cb | ||
|
|
3ba1e33eb6 | ||
|
|
40a1d5fdbf | ||
|
|
1b44218a35 | ||
|
|
df211af604 | ||
|
|
2c723070c6 | ||
|
|
6f5bb3882c | ||
|
|
fc9b175f25 | ||
|
|
1716e46210 | ||
|
|
8800cdf641 | ||
|
|
d998786ea6 | ||
|
|
a50758cc06 | ||
|
|
498cec767b | ||
|
|
8cb580633d | ||
|
|
2b1e060881 | ||
|
|
3702e79a00 | ||
|
|
beb484da10 | ||
|
|
a54cc22da8 | ||
|
|
7f92aca0bd | ||
|
|
8d85ca13df | ||
|
|
fb7e044bd3 | ||
|
|
c08abdb32f | ||
|
|
c55eab6404 | ||
|
|
7680c4091e | ||
|
|
5911563b0f | ||
|
|
eebf878e89 | ||
|
|
a8e02b94d4 | ||
|
|
6ada034c33 | ||
|
|
c01b21e402 | ||
|
|
c8c7aaa459 | ||
|
|
5409cb2927 | ||
|
|
b329513845 | ||
|
|
0ca6e7c6d3 | ||
|
|
bea2415fad | ||
|
|
180269e39e | ||
|
|
8a3adeadd2 | ||
|
|
fc8eedf494 | ||
|
|
c8acba8f5a | ||
|
|
deca6e383a | ||
|
|
f3ac7b8334 | ||
|
|
ad8941ba56 | ||
|
|
2bccfcb725 | ||
|
|
4d174b4c6c | ||
|
|
3b15a79c0a | ||
|
|
b4c2857bb5 | ||
|
|
ba68bd8935 | ||
|
|
734aba5ddb |
No files matched your search
+44
@@ -15,13 +15,17 @@ trans.es_ES = UI/MailerUI/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/MailerUI/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/MailerUI/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/MailerUI/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/MailerUI/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/MailerUI/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/MailerUI/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/MailerUI/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/MailerUI/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/MailerUI/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/MailerUI/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/MailerUI/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/MailerUI/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/MailerUI/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/MailerUI/Slovak.lproj/Localizable.strings
|
||||
@@ -44,13 +48,17 @@ trans.es_ES = UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/PreferencesUI/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/PreferencesUI/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/PreferencesUI/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/PreferencesUI/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/PreferencesUI/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/PreferencesUI/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/PreferencesUI/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/PreferencesUI/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/PreferencesUI/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/PreferencesUI/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/PreferencesUI/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/PreferencesUI/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/PreferencesUI/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/PreferencesUI/Slovak.lproj/Localizable.strings
|
||||
@@ -73,13 +81,17 @@ trans.es_ES = UI/Scheduler/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/Scheduler/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/Scheduler/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/Scheduler/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/Scheduler/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/Scheduler/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/Scheduler/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/Scheduler/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/Scheduler/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/Scheduler/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/Scheduler/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/Scheduler/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/Scheduler/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/Scheduler/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/Scheduler/Slovak.lproj/Localizable.strings
|
||||
@@ -102,13 +114,17 @@ trans.es_ES = UI/Contacts/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/Contacts/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/Contacts/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/Contacts/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/Contacts/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/Contacts/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/Contacts/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/Contacts/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/Contacts/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/Contacts/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/Contacts/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/Contacts/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/Contacts/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/Contacts/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/Contacts/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/Contacts/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/Contacts/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/Contacts/Slovak.lproj/Localizable.strings
|
||||
@@ -131,13 +147,17 @@ trans.es_ES = UI/MainUI/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/MainUI/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/MainUI/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/MainUI/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/MainUI/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/MainUI/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/MainUI/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/MainUI/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/MainUI/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/MainUI/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/MainUI/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/MainUI/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/MainUI/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/MainUI/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/MainUI/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/MainUI/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/MainUI/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/MainUI/Slovak.lproj/Localizable.strings
|
||||
@@ -160,13 +180,17 @@ trans.es_ES = UI/Common/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/Common/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/Common/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/Common/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/Common/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/Common/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/Common/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/Common/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/Common/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/Common/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/Common/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/Common/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/Common/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/Common/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/Common/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/Common/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/Common/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/Common/Slovak.lproj/Localizable.strings
|
||||
@@ -189,13 +213,17 @@ trans.es_ES = UI/AdministrationUI/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/AdministrationUI/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/AdministrationUI/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/AdministrationUI/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/AdministrationUI/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/AdministrationUI/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/AdministrationUI/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/AdministrationUI/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/AdministrationUI/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/AdministrationUI/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/AdministrationUI/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/AdministrationUI/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/AdministrationUI/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/AdministrationUI/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/AdministrationUI/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/AdministrationUI/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/AdministrationUI/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/AdministrationUI/Slovak.lproj/Localizable.strings
|
||||
@@ -218,13 +246,17 @@ trans.es_ES = SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = SoObjects/Appointments/Basque.lproj/Localizable.strings
|
||||
trans.fi = SoObjects/Appointments/Finnish.lproj/Localizable.strings
|
||||
trans.fr = SoObjects/Appointments/French.lproj/Localizable.strings
|
||||
trans.hr_HR = SoObjects/Appointments/Croatian.lproj/Localizable.strings
|
||||
trans.hu = SoObjects/Appointments/Hungarian.lproj/Localizable.strings
|
||||
trans.is = SoObjects/Appointments/Icelandic.lproj/Localizable.strings
|
||||
trans.it = SoObjects/Appointments/Italian.lproj/Localizable.strings
|
||||
trans.lt = SoObjects/Appointments/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = SoObjects/Appointments/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = SoObjects/Appointments/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = SoObjects/Appointments/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = SoObjects/Appointments/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = SoObjects/Appointments/Polish.lproj/Localizable.strings
|
||||
trans.pt = SoObjects/Appointments/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = SoObjects/Appointments/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = SoObjects/Appointments/Russian.lproj/Localizable.strings
|
||||
trans.sk = SoObjects/Appointments/Slovak.lproj/Localizable.strings
|
||||
@@ -247,13 +279,17 @@ trans.es_ES = SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = SoObjects/Contacts/Basque.lproj/Localizable.strings
|
||||
trans.fi = SoObjects/Contacts/Finnish.lproj/Localizable.strings
|
||||
trans.fr = SoObjects/Contacts/French.lproj/Localizable.strings
|
||||
trans.hr_HR = SoObjects/Contacts/Croatian.lproj/Localizable.strings
|
||||
trans.hu = SoObjects/Contacts/Hungarian.lproj/Localizable.strings
|
||||
trans.is = SoObjects/Contacts/Icelandic.lproj/Localizable.strings
|
||||
trans.it = SoObjects/Contacts/Italian.lproj/Localizable.strings
|
||||
trans.lt = SoObjects/Contacts/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = SoObjects/Contacts/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = SoObjects/Contacts/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = SoObjects/Contacts/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = SoObjects/Contacts/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = SoObjects/Contacts/Polish.lproj/Localizable.strings
|
||||
trans.pt = SoObjects/Contacts/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = SoObjects/Contacts/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = SoObjects/Contacts/Russian.lproj/Localizable.strings
|
||||
trans.sk = SoObjects/Contacts/Slovak.lproj/Localizable.strings
|
||||
@@ -276,13 +312,17 @@ trans.es_ES = SoObjects/Mailer/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = SoObjects/Mailer/Basque.lproj/Localizable.strings
|
||||
trans.fi = SoObjects/Mailer/Finnish.lproj/Localizable.strings
|
||||
trans.fr = SoObjects/Mailer/French.lproj/Localizable.strings
|
||||
trans.hr_HR = SoObjects/Mailer/Croatian.lproj/Localizable.strings
|
||||
trans.hu = SoObjects/Mailer/Hungarian.lproj/Localizable.strings
|
||||
trans.is = SoObjects/Mailer/Icelandic.lproj/Localizable.strings
|
||||
trans.it = SoObjects/Mailer/Italian.lproj/Localizable.strings
|
||||
trans.lt = SoObjects/Mailer/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = SoObjects/Mailer/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = SoObjects/Mailer/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = SoObjects/Mailer/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = SoObjects/Mailer/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = SoObjects/Mailer/Polish.lproj/Localizable.strings
|
||||
trans.pt = SoObjects/Mailer/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = SoObjects/Mailer/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = SoObjects/Mailer/Russian.lproj/Localizable.strings
|
||||
trans.sk = SoObjects/Mailer/Slovak.lproj/Localizable.strings
|
||||
@@ -305,13 +345,17 @@ trans.es_ES = UI/MailPartViewers/SpanishSpain.lproj/Localizable.strings
|
||||
trans.eu = UI/MailPartViewers/Basque.lproj/Localizable.strings
|
||||
trans.fi = UI/MailPartViewers/Finnish.lproj/Localizable.strings
|
||||
trans.fr = UI/MailPartViewers/French.lproj/Localizable.strings
|
||||
trans.hr_HR = UI/MailPartViewers/Croatian.lproj/Localizable.strings
|
||||
trans.hu = UI/MailPartViewers/Hungarian.lproj/Localizable.strings
|
||||
trans.is = UI/MailPartViewers/Icelandic.lproj/Localizable.strings
|
||||
trans.it = UI/MailPartViewers/Italian.lproj/Localizable.strings
|
||||
trans.lt = UI/MailPartViewers/Lithuanian.lproj/Localizable.strings
|
||||
trans.mk_MK = UI/MailPartViewers/Macedonian.lproj/Localizable.strings
|
||||
trans.nb_NO = UI/MailPartViewers/NorwegianBokmal.lproj/Localizable.strings
|
||||
trans.nl = UI/MailPartViewers/Dutch.lproj/Localizable.strings
|
||||
trans.nn_NO = UI/MailPartViewers/NorwegianNynorsk.lproj/Localizable.strings
|
||||
trans.pl = UI/MailPartViewers/Polish.lproj/Localizable.strings
|
||||
trans.pt = UI/MailPartViewers/Portuguese.lproj/Localizable.strings
|
||||
trans.pt_BR = UI/MailPartViewers/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = UI/MailPartViewers/Russian.lproj/Localizable.strings
|
||||
trans.sk = UI/MailPartViewers/Slovak.lproj/Localizable.strings
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
# compilation settings
|
||||
ifeq ($(HAS_LIBRARY_ssl),yes)
|
||||
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
|
||||
BUNDLE_LIBS += -lcrypto
|
||||
endif
|
||||
@@ -101,7 +101,7 @@ static NSArray *asElementArray = nil;
|
||||
int i, count;
|
||||
|
||||
if (!asElementArray)
|
||||
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil];
|
||||
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
|
||||
|
||||
data = [NSMutableDictionary dictionary];
|
||||
|
||||
|
||||
+196
-50
@@ -46,13 +46,49 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "NSDate+ActiveSync.h"
|
||||
#include "NSString+ActiveSync.h"
|
||||
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
|
||||
@implementation NGVCard (ActiveSync)
|
||||
|
||||
//
|
||||
// This function is called for each elements which can be ghosted according to specs.
|
||||
// https://msdn.microsoft.com/en-us/library/gg650908%28v=exchg.80%29.aspx
|
||||
//
|
||||
- (BOOL) _isGhosted: (NSString *) element
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
NSArray *supportedElements;
|
||||
|
||||
supportedElements = [context objectForKey: @"SupportedElements"];
|
||||
|
||||
// If the client does not include a Supported element in the initial Sync command request for
|
||||
// a folder, then all of the elements that can be ghosted are considered not ghosted.
|
||||
if (!supportedElements)
|
||||
return NO;
|
||||
|
||||
// If the client includes an empty Supported element in the initial Sync command request for
|
||||
// a folder, then all elements that can be ghosted are considered ghosted.
|
||||
if (![supportedElements count])
|
||||
return YES;
|
||||
|
||||
// If the client includes a Supported element that contains child elements in the initial
|
||||
// Sync command request for a folder, then each child element of that Supported element is
|
||||
// considered not ghosted. All elements that can be ghosted that are not included as child
|
||||
// elements of the Supported element are considered ghosted.
|
||||
if (!([supportedElements indexOfObject: element] == NSNotFound))
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *emails, *addresses, *categories, *elements;
|
||||
CardElement *n, *homeAdr, *workAdr;
|
||||
NSMutableString *s;
|
||||
NSMutableString *s, *a;
|
||||
NSString *url;
|
||||
id o;
|
||||
|
||||
@@ -63,7 +99,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if ((o = [n flattenedValueAtIndex: 0 forKey: @""]))
|
||||
[s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
|
||||
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
|
||||
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
@@ -146,16 +182,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ([addresses count])
|
||||
{
|
||||
homeAdr = [addresses objectAtIndex: 0];
|
||||
a = [NSMutableString string];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""]))
|
||||
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [o activeSyncRepresentationInContext: context]];
|
||||
[a appendString: o];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||
[a appendFormat: @"\n%@", o];
|
||||
|
||||
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""]))
|
||||
[s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""]))
|
||||
[s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
@@ -171,9 +213,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ([addresses count])
|
||||
{
|
||||
workAdr = [addresses objectAtIndex: 0];
|
||||
a = [NSMutableString string];
|
||||
|
||||
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""]))
|
||||
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [o activeSyncRepresentationInContext: context]];
|
||||
[a appendString: o];
|
||||
|
||||
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||
[a appendFormat: @"\n%@", o];
|
||||
|
||||
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
|
||||
@@ -190,7 +238,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Other, less important fields
|
||||
if ((o = [self birthday]))
|
||||
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>", [o activeSyncRepresentationInContext: context]];
|
||||
{
|
||||
NSTimeZone *userTimeZone;
|
||||
userTimeZone = [[[context activeUser] userDefaults] timeZone];
|
||||
|
||||
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>",
|
||||
[[o dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: ([userTimeZone secondsFromGMTForDate: o])*-1]
|
||||
activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
if ((o = [self note]))
|
||||
{
|
||||
@@ -217,6 +274,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
CardElement *element;
|
||||
NSMutableArray *addressLines;
|
||||
id o;
|
||||
|
||||
// Contact's note
|
||||
@@ -226,6 +284,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// Categories
|
||||
if ((o = [theValues objectForKey: @"Categories"]) && [o length])
|
||||
[self setCategories: o];
|
||||
else
|
||||
[[self children] removeObjectsInArray: [self childrenWithTag: @"Categories"]];
|
||||
|
||||
// Birthday
|
||||
if ((o = [theValues objectForKey: @"Birthday"]))
|
||||
@@ -233,6 +293,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
o = [o calendarDate];
|
||||
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
|
||||
}
|
||||
else if (![self _isGhosted: @"Birthday" inContext: context])
|
||||
{
|
||||
[self setBday: @""];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Business address information
|
||||
@@ -244,18 +309,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// BusinessCountry
|
||||
//
|
||||
element = [self elementWithTag: @"adr" ofType: @"work"];
|
||||
[element setSingleValue: @""
|
||||
atIndex: 1 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessStreet"]
|
||||
atIndex: 2 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
|
||||
atIndex: 3 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
|
||||
atIndex: 4 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
|
||||
atIndex: 5 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
|
||||
atIndex: 6 forKey: @""];
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessStreet"]) || ![self _isGhosted: @"BusinessStreet" inContext: context])
|
||||
{
|
||||
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
|
||||
|
||||
[element setSingleValue: @""
|
||||
atIndex: 1 forKey: @""];
|
||||
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
|
||||
atIndex: 2 forKey: @""];
|
||||
|
||||
// Extended address line. If there are more than 2 address lines we add them to the extended address line.
|
||||
if ([addressLines count] > 1)
|
||||
{
|
||||
[addressLines removeObjectAtIndex: 0];
|
||||
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
|
||||
atIndex: 1 forKey: @""];
|
||||
}
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessCity"]) || ![self _isGhosted: @"BusinessCity" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
|
||||
atIndex: 3 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessState"]) || ![self _isGhosted: @"BusinessState" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
|
||||
atIndex: 4 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessPostalCode"]) || ![self _isGhosted: @"BusinessPostalCode" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
|
||||
atIndex: 5 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"BusinessCountry"]) || ![self _isGhosted: @"BusinessCountry" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
|
||||
atIndex: 6 forKey: @""];
|
||||
}
|
||||
|
||||
//
|
||||
// Home address information
|
||||
@@ -267,35 +362,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// HomeCountry
|
||||
//
|
||||
element = [self elementWithTag: @"adr" ofType: @"home"];
|
||||
[element setSingleValue: @""
|
||||
atIndex: 1 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeStreet"]
|
||||
atIndex: 2 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
|
||||
atIndex: 3 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeState"]
|
||||
atIndex: 4 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
|
||||
atIndex: 5 forKey: @""];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
|
||||
atIndex: 6 forKey: @""];
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeStreet"]) || ![self _isGhosted: @"HomeStreet" inContext: context])
|
||||
{
|
||||
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
|
||||
|
||||
[element setSingleValue: @""
|
||||
atIndex: 1 forKey: @""];
|
||||
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
|
||||
atIndex: 2 forKey: @""];
|
||||
|
||||
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
|
||||
if ([addressLines count] > 1)
|
||||
{
|
||||
[addressLines removeObjectAtIndex: 0];
|
||||
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
|
||||
atIndex: 1 forKey: @""];
|
||||
}
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeCity"]) || ![self _isGhosted: @"HomeCity" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
|
||||
atIndex: 3 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeState"]) || ![self _isGhosted: @"HomeState" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeState"]
|
||||
atIndex: 4 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomePostalCode"]) || ![self _isGhosted: @"HomePostalCode" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
|
||||
atIndex: 5 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"HomeCountry"]) || ![self _isGhosted: @"HomeCountry" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
|
||||
atIndex: 6 forKey: @""];
|
||||
}
|
||||
|
||||
// Company's name
|
||||
if ((o = [theValues objectForKey: @"CompanyName"]))
|
||||
[self setOrg: o units: nil];
|
||||
else if (![self _isGhosted: @"CompanyName" inContext: context])
|
||||
[self setOrg: @"" units: nil];
|
||||
|
||||
// Department
|
||||
if ((o = [theValues objectForKey: @"Department"]))
|
||||
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
|
||||
else if (![self _isGhosted: @"Department" inContext: context])
|
||||
[self setOrg: nil units: [NSArray arrayWithObjects:@"",nil]];
|
||||
|
||||
// Email addresses
|
||||
if ((o = [theValues objectForKey: @"Email1Address"]))
|
||||
if ((o = [theValues objectForKey: @"Email1Address"]) || ![self _isGhosted: @"Email1Address" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"email" ofType: @"work"];
|
||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"Email2Address"]))
|
||||
if ((o = [theValues objectForKey: @"Email2Address"]) || ![self _isGhosted: @"Email2Address" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"email" ofType: @"home"];
|
||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||
@@ -303,7 +432,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
|
||||
// FIXME: revise this when the GUI revamp is done in SOGo
|
||||
if ((o = [theValues objectForKey: @"Email3Address"]))
|
||||
if ((o = [theValues objectForKey: @"Email3Address"]) || ![self _isGhosted: @"Email3Address" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"email" ofType: @"three"];
|
||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||
@@ -313,45 +442,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// MiddleName
|
||||
// Suffix (II)
|
||||
// Title (Mr.)
|
||||
[self setFn: [theValues objectForKey: @"FileAs"]];
|
||||
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
|
||||
[self setFn: [theValues objectForKey: @"FileAs"]];
|
||||
|
||||
[self setNWithFamily: [theValues objectForKey: @"LastName"]
|
||||
given: [theValues objectForKey: @"FirstName"]
|
||||
additional: nil prefixes: nil suffixes: nil];
|
||||
|
||||
// IM information
|
||||
[[self uniqueChildWithTag: @"x-aim"]
|
||||
setSingleValue: [theValues objectForKey: @"IMAddress"]
|
||||
forKey: @""];
|
||||
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
|
||||
[[self uniqueChildWithTag: @"x-aim"]
|
||||
setSingleValue: [theValues objectForKey: @"IMAddress"]
|
||||
forKey: @""];
|
||||
|
||||
//
|
||||
// Phone numbrrs
|
||||
//
|
||||
element = [self elementWithTag: @"tel" ofType: @"work"];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
|
||||
if ((o = [theValues objectForKey: @"BusinessPhoneNumber"]) || ![self _isGhosted: @"BusinessPhoneNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"work"];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
element = [self elementWithTag: @"tel" ofType: @"home"];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
|
||||
if ((o = [theValues objectForKey: @"HomePhoneNumber"]) || ![self _isGhosted: @"HomePhoneNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"home"];
|
||||
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
element = [self elementWithTag: @"tel" ofType: @"cell"];
|
||||
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
|
||||
if ((o = [theValues objectForKey: @"MobilePhoneNumber"]) || ![self _isGhosted: @"MobilePhoneNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"cell"];
|
||||
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
element = [self elementWithTag: @"tel" ofType: @"fax"];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
|
||||
if ((o = [theValues objectForKey: @"BusinessFaxNumber"]) || ![self _isGhosted: @"BusinessFaxNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"fax"];
|
||||
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
element = [self elementWithTag: @"tel" ofType: @"pager"];
|
||||
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
|
||||
if ((o = [theValues objectForKey: @"PagerNumber"]) || ![self _isGhosted: @"PagerNumber" inContext: context])
|
||||
{
|
||||
element = [self elementWithTag: @"tel" ofType: @"pager"];
|
||||
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
|
||||
}
|
||||
|
||||
// Job's title
|
||||
if ((o = [theValues objectForKey: @"JobTitle"]))
|
||||
if ((o = [theValues objectForKey: @"JobTitle"]) || ![self _isGhosted: @"JobTitle" inContext: context])
|
||||
[self setTitle: o];
|
||||
|
||||
// WebPage (work)
|
||||
if ((o = [theValues objectForKey: @"WebPage"]))
|
||||
if ((o = [theValues objectForKey: @"WebPage"]) || ![self _isGhosted: @"WebPage" inContext: context])
|
||||
[[self elementWithTag: @"url" ofType: @"work"]
|
||||
setSingleValue: o forKey: @""];
|
||||
|
||||
if ((o = [theValues objectForKey: @"NickName"]))
|
||||
if ((o = [theValues objectForKey: @"NickName"]) || ![self _isGhosted: @"NickName" inContext: context])
|
||||
[self setNickname: o];
|
||||
|
||||
if ((o = [theValues objectForKey: @"Picture"]))
|
||||
|
||||
@@ -75,7 +75,7 @@ static NSArray *easCommandParameters = nil;
|
||||
escapeCount++;
|
||||
break;
|
||||
default:
|
||||
if (chars[i] > 127)
|
||||
if (chars[i] < 0x20 || chars[i] > 127)
|
||||
escapeCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/SOGoCacheGCSObject.h>
|
||||
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
#import <Appointments/SOGoAppointmentObject.h>
|
||||
#import <Appointments/SOGoAppointmentFolder.h>
|
||||
#import <Appointments/SOGoAppointmentFolders.h>
|
||||
@@ -112,13 +113,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@implementation SOGoActiveSyncDispatcher (Sync)
|
||||
|
||||
- (void) _setOrUnsetSyncRequest: (BOOL) set
|
||||
collections: (NSArray *) collections
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
NSNumber *processIdentifier;
|
||||
NSString *key;
|
||||
int i;
|
||||
|
||||
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
if (set)
|
||||
{
|
||||
RELEASE(syncRequest);
|
||||
syncRequest = [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]];
|
||||
RETAIN(syncRequest);
|
||||
|
||||
[[o properties] setObject: syncRequest forKey: @"SyncRequest"];
|
||||
|
||||
for (i = 0; i < [collections count]; i++)
|
||||
{
|
||||
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||
[[o properties] setObject: processIdentifier forKey: key];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[[o properties] removeObjectForKey: @"SyncRequest"];
|
||||
for (i = 0; i < [collections count]; i++)
|
||||
{
|
||||
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||
[[o properties] removeObjectForKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
|
||||
forKey: (NSString *) theFolderKey
|
||||
{
|
||||
NSNumber *processIdentifier, *processIdentifierInCache;
|
||||
SOGoCacheGCSObject *o;
|
||||
NSDictionary *values;
|
||||
NSString *key;
|
||||
|
||||
if ([theFolderKey hasPrefix: @"folder"])
|
||||
key = [NSString stringWithFormat: @"SyncRequest+mail/%@", [theFolderKey substringFromIndex: 6]];
|
||||
else
|
||||
key = [NSString stringWithFormat: @"SyncRequest+%@", theFolderKey];
|
||||
|
||||
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||
processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key];
|
||||
|
||||
// Don't update the cache if another request is processing the same collection.
|
||||
if (!([processIdentifierInCache isEqual: processIdentifier]))
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - We lost our lock - discard folder cache update %@ %@ <> %@", key, processIdentifierInCache, processIdentifier];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
||||
values = [theFolderMetadata copy];
|
||||
|
||||
@@ -132,6 +193,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
|
||||
@@ -404,38 +466,47 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[o takeActiveSyncValues: allChanges inContext: context];
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
|
||||
if ([syncCache objectForKey: serverId])
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
}
|
||||
break;
|
||||
case ActiveSyncEventFolder:
|
||||
case ActiveSyncTaskFolder:
|
||||
{
|
||||
o = [sogoObject component: NO secure: NO];
|
||||
[o takeActiveSyncValues: allChanges inContext: context];
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
if (theFolderType == ActiveSyncEventFolder &&
|
||||
[(iCalEvent *)o userIsAttendee: [context activeUser]])
|
||||
{
|
||||
[o changeParticipationStatus: allChanges inContext: context component: sogoObject];
|
||||
}
|
||||
else
|
||||
{
|
||||
[o takeActiveSyncValues: allChanges inContext: context];
|
||||
[sogoObject saveComponent: o];
|
||||
}
|
||||
|
||||
if ([syncCache objectForKey: serverId])
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
}
|
||||
break;
|
||||
case ActiveSyncMailFolder:
|
||||
default:
|
||||
{
|
||||
NSDictionary *result;
|
||||
NSString *modseq;
|
||||
NSNumber *modseq;
|
||||
|
||||
[sogoObject takeActiveSyncValues: allChanges inContext: context];
|
||||
|
||||
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
|
||||
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
|
||||
|
||||
if (modseq)
|
||||
[syncCache setObject: modseq forKey: serverId];
|
||||
if (modseq && [syncCache objectForKey: serverId])
|
||||
[syncCache setObject: [modseq stringValue] forKey: serverId];
|
||||
}
|
||||
}
|
||||
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
|
||||
[theBuffer appendString: @"<Change>"];
|
||||
@@ -512,6 +583,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// FIXME: handle errors here
|
||||
if (deletesAsMoves && theFolderType == ActiveSyncMailFolder)
|
||||
[(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context];
|
||||
else if (theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder)
|
||||
{
|
||||
[sogoObject prepareDelete];
|
||||
[sogoObject delete];
|
||||
}
|
||||
else
|
||||
[sogoObject delete];
|
||||
}
|
||||
@@ -552,6 +628,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/gg675490%28v=exchg.80%29.aspx
|
||||
// The fetch element is used to request the application data of an item that was truncated in a synchronization response from the server.
|
||||
// The complete item is then returned to the client in a server response.
|
||||
[context setObject: @"8" forKey: @"MIMETruncation"];
|
||||
|
||||
o = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
@@ -578,36 +659,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
withFilterType: (NSCalendarDate *) theFilterType
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
lastServerKey: (NSString **) theLastServerKey
|
||||
|
||||
defaultInterval: (unsigned int) theDefaultInterval
|
||||
{
|
||||
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
|
||||
NSString *davCollectionTagToStore;
|
||||
NSAutoreleasePool *pool;
|
||||
NSMutableString *s;
|
||||
|
||||
BOOL more_available;
|
||||
BOOL cleanup_needed, more_available;
|
||||
int i, max;
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
more_available = NO;
|
||||
cleanup_needed = more_available = NO;
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
// If this is a new sync operation, DateCache and SyncCache needs to be deleted
|
||||
// If this is a new sync operation, DateCache and SyncCache need to be deleted
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
|
||||
}
|
||||
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
|
||||
{
|
||||
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
|
||||
// We need to cleanup this mess.
|
||||
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@ syncKey: %@ cache: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login], theSyncKey, [folderMetadata objectForKey: @"SyncKey"]];
|
||||
cleanup_needed = YES;
|
||||
}
|
||||
|
||||
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
||||
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
||||
|
||||
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
|
||||
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
||||
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation
|
||||
theFilterType)
|
||||
(cleanup_needed ||
|
||||
( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
||||
!([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
|
||||
theFilterType
|
||||
)
|
||||
{
|
||||
NSArray *allKeys;
|
||||
NSString *key;
|
||||
@@ -623,14 +712,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
|
||||
{
|
||||
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
||||
[s appendString: @"</SoftDelete>"];
|
||||
if ([syncCache objectForKey:key])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete %@", key];
|
||||
|
||||
[syncCache removeObjectForKey: key];
|
||||
[dateCache removeObjectForKey: key];
|
||||
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
||||
[s appendString: @"</SoftDelete>"];
|
||||
|
||||
[syncCache removeObjectForKey: key];
|
||||
//[dateCache removeObjectForKey: key];
|
||||
|
||||
softdelete_count++;
|
||||
softdelete_count++;
|
||||
}
|
||||
else if (cleanup_needed)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
|
||||
|
||||
// With this we make sure that a SoftDelete is set again on next sync.
|
||||
[syncCache setObject: @"0" forKey: key];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
|
||||
|
||||
// Now we are save to remove the dateCache entry.
|
||||
[dateCache removeObjectForKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
||||
@@ -657,8 +768,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
|
||||
return;
|
||||
|
||||
more_available = NO;
|
||||
|
||||
davCollectionTagToStore = [theCollection davCollectionTag];
|
||||
|
||||
@@ -687,7 +796,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
@@ -697,13 +806,63 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil
|
||||
matchingSyncToken: theSyncKey
|
||||
fromDate: theFilterType
|
||||
initialLoad: initialLoadInProgress];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
|
||||
|
||||
|
||||
// Check for the WindowSize
|
||||
max = [allComponents count];
|
||||
|
||||
//
|
||||
// Cleanup the mess
|
||||
//
|
||||
if (cleanup_needed)
|
||||
{
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
component = [allComponents objectAtIndex: i];
|
||||
deleted = [[component objectForKey: @"c_deleted"] intValue];
|
||||
|
||||
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
|
||||
continue;
|
||||
|
||||
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", uid];
|
||||
|
||||
// For deletes we have to recreate a cache entry to make sure the delete is sent again.
|
||||
[syncCache setObject: @"0" forKey: uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([syncCache objectForKey: uid] && [[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: ADD %@", uid];
|
||||
|
||||
// Cleanup the cache to make sure the add is sent again.
|
||||
[syncCache removeObjectForKey: uid];
|
||||
[dateCache removeObjectForKey: uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", uid];
|
||||
|
||||
// Update cache entry to make sure the change is sent again.
|
||||
[syncCache setObject: @"0" forKey: uid];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return_count = 0;
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
@@ -767,35 +926,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if (theFolderType == ActiveSyncContactFolder)
|
||||
componentObject = [sogoObject vCard];
|
||||
else
|
||||
componentObject = [sogoObject component: NO secure: NO];
|
||||
|
||||
//
|
||||
// We do NOT synchronize NEW events that are in fact, invitations
|
||||
// to events. This is due to the fact that Outlook 2013 creates
|
||||
// "phantom" events in the calendar that are mapped to invitations mails.
|
||||
// If we synchronize these events too, it'll interfere with the whole thing
|
||||
// and prevent Outlook from properly calling MeetingResponse.
|
||||
//
|
||||
if (!updated && theFolderType == ActiveSyncEventFolder)
|
||||
{
|
||||
iCalPersonPartStat partstat;
|
||||
iCalPerson *attendee;
|
||||
NSString *email;
|
||||
|
||||
email = [[[context activeUser] allEmails] objectAtIndex: 0];
|
||||
attendee = [componentObject findAttendeeWithEmail: email];
|
||||
|
||||
if (attendee)
|
||||
{
|
||||
partstat = [attendee participationStatus];
|
||||
|
||||
if (partstat == iCalPersonPartStatNeedsAction)
|
||||
{
|
||||
DESTROY(pool);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
componentObject = [sogoObject component: NO secure: NO];
|
||||
|
||||
[syncCache setObject: [component objectForKey: @"c_lastmodified"] forKey: uid];
|
||||
|
||||
@@ -862,11 +993,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
SOGoMailObject *mailObject;
|
||||
NSArray *allMessages, *a;
|
||||
NSString *firstUIDAdded;
|
||||
|
||||
int j, k, return_count, highestmodseq;
|
||||
BOOL found_in_cache, initialLoadInProgress;
|
||||
|
||||
initialLoadInProgress = NO;
|
||||
initialLoadInProgress = NO;
|
||||
found_in_cache = NO;
|
||||
firstUIDAdded = nil;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
@@ -897,7 +1031,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
|
||||
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
||||
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
||||
}
|
||||
|
||||
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||
@@ -906,15 +1040,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
|
||||
|
||||
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]);
|
||||
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]);
|
||||
if (debugOn)
|
||||
{
|
||||
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
|
||||
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
|
||||
}
|
||||
|
||||
lastCacheObject = [sortedBySequence lastObject];
|
||||
|
||||
//
|
||||
// Cleanup the mess
|
||||
//
|
||||
if (cleanup_needed)
|
||||
{
|
||||
NSMutableArray *sortedByUID;
|
||||
int uidnextFromCache;
|
||||
|
||||
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||
[sortedByUID sortUsingSelector: @selector(compareUID:)];
|
||||
|
||||
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
|
||||
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
|
||||
uidnextFromCache = [[a objectAtIndex: 0] intValue];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
|
||||
|
||||
// Remove all entries from cache beginning with the first uid added by the last sync request.
|
||||
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
|
||||
|
||||
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||
}
|
||||
|
||||
RELEASE(sortedByUID);
|
||||
|
||||
for (j = 0; j < [allCacheObjects count]; j++)
|
||||
{
|
||||
// Update the modseq in cache, sence othersie, it would be identical to the modseq from server
|
||||
//and we would skip the cache when generating the response.
|
||||
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||
|
||||
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||
}
|
||||
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||
|
||||
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
|
||||
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject)
|
||||
if (!cleanup_needed &&
|
||||
[folderMetadata objectForKey: @"MoreAvailable"] &&
|
||||
lastCacheObject &&
|
||||
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
|
||||
{
|
||||
for (j = 0; j < [allCacheObjects count]; j++)
|
||||
{
|
||||
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
|
||||
(![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
|
||||
{
|
||||
// We need to continue with adds or deletes from here.
|
||||
found_in_cache = YES;
|
||||
j--;
|
||||
break;
|
||||
}
|
||||
|
||||
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
|
||||
{
|
||||
// Found out where we're at, let's continue from there...
|
||||
@@ -929,12 +1130,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if (found_in_cache)
|
||||
k = j+1;
|
||||
else
|
||||
{
|
||||
k = 0;
|
||||
j = 0;
|
||||
}
|
||||
|
||||
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
|
||||
j = k = 0;
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
|
||||
|
||||
return_count = 0;
|
||||
|
||||
@@ -948,20 +1147,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *lastSequence;
|
||||
more_available = YES;
|
||||
|
||||
if (!firstUIDAdded)
|
||||
{
|
||||
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||
firstUIDAdded = [a objectAtIndex: 0];
|
||||
RETAIN(firstUIDAdded);
|
||||
}
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, lastSequence];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
|
||||
|
||||
DESTROY(pool);
|
||||
break;
|
||||
}
|
||||
|
||||
aCacheObject = [allCacheObjects objectAtIndex: k];
|
||||
|
||||
// If found in cache, it's either a Change or a Delete
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
|
||||
|
||||
// If found in cache, it's either a Change or a Delete operation.
|
||||
if ([syncCache objectForKey: [aCacheObject uid]])
|
||||
{
|
||||
if ([[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - DELETE!"];
|
||||
|
||||
// Deleted
|
||||
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
@@ -979,9 +1193,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
mailObject = [theCollection lookupName: [aCacheObject uid]
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
|
||||
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - CHANGE!"];
|
||||
|
||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
@@ -997,6 +1214,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - ADD!"];
|
||||
|
||||
// Added
|
||||
if (![[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
@@ -1029,11 +1249,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
||||
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
|
||||
|
||||
// Save the frist UID we add. We will use it for the synckey late.
|
||||
if (!firstUIDAdded)
|
||||
{
|
||||
firstUIDAdded = [aCacheObject uid];
|
||||
RETAIN(firstUIDAdded);
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - first uid added %@", firstUIDAdded];
|
||||
}
|
||||
|
||||
return_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]);
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1042,16 +1273,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (more_available)
|
||||
{
|
||||
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
||||
|
||||
if (firstUIDAdded)
|
||||
{
|
||||
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
|
||||
RELEASE(firstUIDAdded);
|
||||
}
|
||||
else
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
||||
}
|
||||
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
RELEASE(*theLastServerKey);
|
||||
|
||||
} // default:
|
||||
@@ -1083,8 +1323,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
processed: (BOOL *) processed
|
||||
{
|
||||
id <DOMNodeList> aCommandDetails;
|
||||
id <DOMElement> aCommand, element;
|
||||
id <DOMNodeList> aCommandDetails;
|
||||
NSAutoreleasePool *pool;
|
||||
NSArray *allCommands;
|
||||
int i, j;
|
||||
|
||||
@@ -1097,6 +1338,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
for (j = 0; j < [(id)aCommandDetails count]; j++)
|
||||
{
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
element = [aCommandDetails objectAtIndex: j];
|
||||
|
||||
if ([element nodeType] == DOM_ELEMENT_NODE)
|
||||
@@ -1138,6 +1380,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*processed = YES;
|
||||
}
|
||||
}
|
||||
DESTROY(pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1150,15 +1393,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
changeDetected: (BOOL *) changeDetected
|
||||
maxSyncResponseSize: (int) theMaxSyncResponseSize
|
||||
{
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache;
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
id collection, value;
|
||||
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
BOOL getChanges, first_sync;
|
||||
unsigned int windowSize, v, status;
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *mimeTruncation, *lastServerKey, *syncKeyInCache, *folderKey;
|
||||
NSMutableDictionary *folderMetadata, *folderOptions;
|
||||
|
||||
NSMutableArray *supportedElements, *supportedElementNames;
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
id collection, value;
|
||||
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
unsigned int windowSize, v, status, i;
|
||||
BOOL getChanges, first_sync;
|
||||
|
||||
changeBuffer = [NSMutableString string];
|
||||
commandsBuffer = [NSMutableString string];
|
||||
|
||||
@@ -1181,7 +1425,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//[theBuffer appendString: @"</Collection>"];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// First check if we have any concurrent Sync requests going on for this device.
|
||||
// If we do and we are still within our maximumSyncInterval, we let our EAS
|
||||
// device know to retry.
|
||||
//
|
||||
folderKey = [self _getNameInCache: collection withType: folderType];
|
||||
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||
|
||||
// We check for a window size, default to 100 if not specfied or out of bounds
|
||||
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
|
||||
|
||||
@@ -1206,13 +1458,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
first_sync = NO;
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
||||
|
||||
if ([syncKey isEqualToString: @"0"])
|
||||
{
|
||||
davCollectionTag = @"-1";
|
||||
first_sync = YES;
|
||||
*changeDetected = YES;
|
||||
|
||||
supportedElementNames = [[[NSMutableArray alloc] init] autorelease];
|
||||
value = [theDocumentElement getElementsByTagName: @"Supported"];
|
||||
|
||||
if ([value count])
|
||||
{
|
||||
supportedElements = (id)[[value lastObject] childNodes];
|
||||
|
||||
if ([supportedElements count])
|
||||
{
|
||||
for (i = 0; i < [supportedElements count]; i++)
|
||||
{
|
||||
if ([[supportedElements objectAtIndex: i] nodeType] == DOM_ELEMENT_NODE)
|
||||
[supportedElementNames addObject: [[supportedElements objectAtIndex: i] tagName]];
|
||||
}
|
||||
}
|
||||
|
||||
[folderMetadata setObject: supportedElementNames forKey: @"SupportedElements"];
|
||||
|
||||
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - %d %@: supportedElements saved: %@", [supportedElements count], [collection nameInContainer], supportedElementNames];
|
||||
}
|
||||
}
|
||||
else if ((![syncKey isEqualToString: @"-1"]) && !([folderMetadata objectForKey: @"SyncCache"]))
|
||||
{
|
||||
@@ -1231,42 +1505,61 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
||||
|
||||
if (!bodyPreferenceType)
|
||||
{
|
||||
bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"];
|
||||
{
|
||||
bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"];
|
||||
|
||||
// By default, send MIME mails. See #3146 for details.
|
||||
if (!bodyPreferenceType)
|
||||
bodyPreferenceType = @"4";
|
||||
}
|
||||
// By default, send MIME mails. See #3146 for details.
|
||||
if (!bodyPreferenceType)
|
||||
bodyPreferenceType = @"4";
|
||||
|
||||
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
||||
mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"];
|
||||
|
||||
if (!mimeSupport)
|
||||
mimeSupport = @"1";
|
||||
|
||||
if (!mimeTruncation)
|
||||
mimeTruncation = @"8";
|
||||
}
|
||||
else
|
||||
{
|
||||
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
||||
{
|
||||
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
||||
mimeTruncation = [[(id)[theDocumentElement getElementsByTagName: @"MIMETruncation"] lastObject] textValue];
|
||||
|
||||
if (!mimeSupport)
|
||||
if (!mimeSupport)
|
||||
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
||||
|
||||
if (!mimeSupport)
|
||||
if (!mimeSupport)
|
||||
mimeSupport = @"0";
|
||||
|
||||
if ([mimeSupport isEqualToString: @"1"] && [bodyPreferenceType isEqualToString: @"4"])
|
||||
if (!mimeTruncation)
|
||||
mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"];
|
||||
|
||||
if (!mimeTruncation)
|
||||
mimeTruncation = @"8";
|
||||
|
||||
if ([mimeSupport isEqualToString: @"1"] && [bodyPreferenceType isEqualToString: @"4"])
|
||||
bodyPreferenceType = @"2";
|
||||
else if ([mimeSupport isEqualToString: @"2"] && [bodyPreferenceType isEqualToString: @"4"])
|
||||
else if ([mimeSupport isEqualToString: @"2"] && [bodyPreferenceType isEqualToString: @"4"])
|
||||
bodyPreferenceType = @"4";
|
||||
else if ([mimeSupport isEqualToString: @"0"] && [bodyPreferenceType isEqualToString: @"4"])
|
||||
else if ([mimeSupport isEqualToString: @"0"] && [bodyPreferenceType isEqualToString: @"4"])
|
||||
bodyPreferenceType = @"2";
|
||||
|
||||
|
||||
// Avoid writing to cache if there is nothing to change.
|
||||
if (![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"] isEqualToString: bodyPreferenceType] ||
|
||||
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"] isEqualToString: mimeSupport])
|
||||
{
|
||||
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
|
||||
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]];
|
||||
}
|
||||
}
|
||||
// Avoid writing to cache if there is nothing to change.
|
||||
if (![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"] isEqualToString: bodyPreferenceType] ||
|
||||
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"] isEqualToString: mimeSupport] ||
|
||||
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"] isEqualToString: mimeTruncation])
|
||||
{
|
||||
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", mimeTruncation, @"MIMETruncation", bodyPreferenceType, @"BodyPreferenceType", nil];
|
||||
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
||||
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||
}
|
||||
}
|
||||
|
||||
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
||||
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
||||
[context setObject: mimeTruncation forKey: @"MIMETruncation"];
|
||||
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
|
||||
|
||||
//
|
||||
// We process the commands from the request
|
||||
@@ -1305,10 +1598,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
withFolderType: folderType
|
||||
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
|
||||
inBuffer: changeBuffer
|
||||
lastServerKey: &lastServerKey];
|
||||
lastServerKey: &lastServerKey
|
||||
defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval]];
|
||||
}
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
||||
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||
|
||||
// If we got any changes or if we have applied any commands
|
||||
// let's regenerate our SyncKey based on the collection tag.
|
||||
@@ -1467,19 +1761,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
SOGoSystemDefaults *defaults;
|
||||
id <DOMElement> aCollection;
|
||||
NSMutableString *output, *s;
|
||||
NSMutableDictionary *globalMetadata;
|
||||
NSNumber *syncRequestInCache, *processIdentifier;
|
||||
NSString *key;
|
||||
NSArray *allCollections;
|
||||
NSData *d;
|
||||
|
||||
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize;
|
||||
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
|
||||
BOOL changeDetected;
|
||||
|
||||
changeDetected = NO;
|
||||
|
||||
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
||||
|
||||
// We initialize our output buffer
|
||||
output = [[NSMutableString alloc] init];
|
||||
|
||||
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
defaultInterval = [defaults maximumSyncInterval];
|
||||
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||
|
||||
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
||||
|
||||
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
|
||||
@@ -1494,12 +1793,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[output appendString: @"</Sync>"];
|
||||
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
[theResponse setContent: d];
|
||||
RELEASE(output);
|
||||
return;
|
||||
}
|
||||
|
||||
// Let other requests know about the collections we are dealing with.
|
||||
[self _setOrUnsetSyncRequest: YES collections: allCollections];
|
||||
|
||||
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
changeDetected = NO;
|
||||
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
||||
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
|
||||
defaultInterval = [defaults maximumSyncInterval];
|
||||
internalInterval = [defaults internalSyncInterval];
|
||||
|
||||
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
|
||||
@@ -1509,17 +1812,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// We check to see if our heartbeat interval falls into the supported ranges.
|
||||
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
|
||||
{
|
||||
int limit;
|
||||
// Interval is too long, inform the client.
|
||||
heartbeatInterval = defaultInterval;
|
||||
|
||||
// Outlook doesn't like this...
|
||||
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval];
|
||||
// When Status = 14, the Wait interval is specified in minutes while
|
||||
// defaultInterval is specifed in seconds. Adjust accordinlgy.
|
||||
limit = defaultInterval/60;
|
||||
if (limit < 1) limit = 1;
|
||||
if (limit > 59) limit = 59;
|
||||
//[output appendFormat: @"<Limit>%d</Limit>", limit];
|
||||
//[output appendFormat: @"<Status>%d</Status>", 14];
|
||||
}
|
||||
|
||||
[output appendString: @"<Collections>"];
|
||||
|
||||
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
||||
|
||||
// We enter our loop detection change
|
||||
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
|
||||
@@ -1529,25 +1835,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
for (j = 0; j < [allCollections count]; j++)
|
||||
{
|
||||
aCollection = [allCollections objectAtIndex: j];
|
||||
|
||||
|
||||
[self processSyncCollection: aCollection
|
||||
inBuffer: s
|
||||
changeDetected: &changeDetected
|
||||
maxSyncResponseSize: maxSyncResponseSize];
|
||||
|
||||
if (maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize)
|
||||
// Don't return a response if another Sync is waiting.
|
||||
globalMetadata = [self globalMetadataForDevice];
|
||||
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||
|
||||
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
|
||||
|
||||
[theResponse setStatus: 503];
|
||||
|
||||
RELEASE(output);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize))
|
||||
break;
|
||||
}
|
||||
|
||||
if (changeDetected)
|
||||
{
|
||||
[self logWithFormat: @"Change detected, we push the content."];
|
||||
[self logWithFormat: @"Change detected during Sync, we push the content."];
|
||||
break;
|
||||
}
|
||||
else if (heartbeatInterval > 1)
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
||||
sleep(internalInterval);
|
||||
total_sleep = 0;
|
||||
|
||||
while (total_sleep < internalInterval)
|
||||
{
|
||||
// We check if we must break the current synchronization since an other Sync
|
||||
// has just arrived.
|
||||
syncRequestInCache = [[self globalMetadataForDevice] objectForKey: @"SyncRequest"];
|
||||
if (!([syncRequest isEqualToNumber: syncRequestInCache]))
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
|
||||
|
||||
// Make sure we end the heardbeat-loop.
|
||||
heartbeatInterval = internalInterval = 1;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Sync...", internalInterval-total_sleep];
|
||||
sleep(5);
|
||||
total_sleep += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1555,8 +1898,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response.
|
||||
//
|
||||
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
|
||||
// otherwise send an empty response.
|
||||
//
|
||||
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
|
||||
{
|
||||
// We always return the last generated response.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
Copyright (c) 2014-2015, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -31,16 +31,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "SOGoActiveSyncConstants.h"
|
||||
|
||||
@class NSCalendarDate;
|
||||
@class NSException;
|
||||
@class NSMutableDictionary;
|
||||
@class NSURL;
|
||||
@class NSNumber;
|
||||
|
||||
@interface SOGoActiveSyncDispatcher : NSObject
|
||||
{
|
||||
NSURL *folderTableURL;
|
||||
NSDictionary *imapFolderGUIDS;
|
||||
id context;
|
||||
|
||||
NSNumber *syncRequest;
|
||||
|
||||
BOOL debugOn;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) globalMetadataForDevice;
|
||||
|
||||
- (id) collectionFromId: (NSString *) theCollectionId
|
||||
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
|
||||
|
||||
|
||||
@@ -30,8 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "SOGoActiveSyncDispatcher.h"
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#if GNUSTEP_BASE_MINOR_VERSION >= 21
|
||||
#import <Foundation/NSLocale.h>
|
||||
#endif
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
@@ -134,6 +138,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
#endif
|
||||
|
||||
@interface SOGoActiveSyncDispatcher (Sync)
|
||||
|
||||
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
|
||||
@@ -143,8 +153,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@implementation SOGoActiveSyncDispatcher
|
||||
|
||||
static BOOL debugOn = NO;
|
||||
|
||||
- (id) init
|
||||
{
|
||||
[super init];
|
||||
@@ -152,6 +160,7 @@ static BOOL debugOn = NO;
|
||||
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
||||
folderTableURL = nil;
|
||||
imapFolderGUIDS = nil;
|
||||
syncRequest = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -159,6 +168,7 @@ static BOOL debugOn = NO;
|
||||
{
|
||||
RELEASE(folderTableURL);
|
||||
RELEASE(imapFolderGUIDS);
|
||||
RELEASE(syncRequest);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -171,16 +181,16 @@ static BOOL debugOn = NO;
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
[[o properties] removeAllObjects];
|
||||
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
|
||||
[[o properties] setObject: theSyncKey
|
||||
forKey: @"FolderSyncKey"];
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) _globalMetadataForDevice
|
||||
- (NSMutableDictionary *) globalMetadataForDevice
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
@@ -204,7 +214,7 @@ static BOOL debugOn = NO;
|
||||
if (theFilter)
|
||||
{
|
||||
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
@@ -377,7 +387,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties ] setObject: [[newFolder nameInContainer] substringFromIndex: 6] forKey: @"displayName"];
|
||||
[o save];
|
||||
|
||||
nameInContainer = [[NSString stringWithFormat: @"mail/%@", [nameInContainer substringFromIndex: 6]] stringByEscapingURL];
|
||||
nameInContainer = [NSString stringWithFormat: @"mail/%@", [nameInContainer substringFromIndex: 6]];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -393,17 +403,24 @@ static BOOL debugOn = NO;
|
||||
SOGoAppointmentFolders *appointmentFolders;
|
||||
SOGoCacheGCSObject *o;
|
||||
NSString *key;
|
||||
id newFolder;
|
||||
|
||||
nameInContainer = nil;
|
||||
|
||||
appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context];
|
||||
[appointmentFolders newFolderWithName: displayName
|
||||
nameInContainer: &nameInContainer];
|
||||
|
||||
newFolder = [appointmentFolders lookupName: nameInContainer
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
[newFolder setSynchronize: YES];
|
||||
|
||||
if (type == 13)
|
||||
nameInContainer = [NSString stringWithFormat: @"vevent/%@", nameInContainer];
|
||||
else
|
||||
nameInContainer = [NSString stringWithFormat: @"vtodo/%@", nameInContainer];
|
||||
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
@@ -418,12 +435,19 @@ static BOOL debugOn = NO;
|
||||
SOGoContactFolders *contactFolders;
|
||||
SOGoCacheGCSObject *o;
|
||||
NSString *key;
|
||||
id newFolder;
|
||||
|
||||
nameInContainer = nil;
|
||||
|
||||
contactFolders = [userFolder privateContacts: @"Contacts" inContext: context];
|
||||
[contactFolders newFolderWithName: displayName
|
||||
nameInContainer: &nameInContainer];
|
||||
|
||||
newFolder = [contactFolders lookupName: nameInContainer
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
[newFolder setSynchronize: YES];
|
||||
|
||||
nameInContainer = [NSString stringWithFormat: @"vcard/%@", nameInContainer];
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
|
||||
@@ -697,26 +721,25 @@ static BOOL debugOn = NO;
|
||||
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType;
|
||||
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
|
||||
NSMutableDictionary *cachedGUIDs, *metadata;
|
||||
NSMutableArray *folders, *processedFolders;
|
||||
NSDictionary *folderMetadata, *imapGUIDs;
|
||||
NSArray *allFoldersMetadata, *allKeys;
|
||||
NSMutableDictionary *cachedGUIDs, *metadata;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
NSMutableString *s, *commands;
|
||||
SOGoUserFolder *userFolder;
|
||||
NSMutableArray *folders, *processedFolders;
|
||||
SoSecurityManager *sm;
|
||||
SOGoCacheGCSObject *o;
|
||||
id currentFolder;
|
||||
NSData *d;
|
||||
|
||||
int status, command_count, i, type, fi, count;
|
||||
|
||||
BOOL first_sync;
|
||||
|
||||
sm = [SoSecurityManager sharedSecurityManager];
|
||||
metadata = [self _globalMetadataForDevice];
|
||||
metadata = [self globalMetadataForDevice];
|
||||
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
s = [NSMutableString string];
|
||||
|
||||
@@ -830,32 +853,38 @@ static BOOL debugOn = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
|
||||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
folderType = @"Calendar";
|
||||
else
|
||||
folderType = @"Contacts";
|
||||
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
|
||||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
folderType = @"Calendar";
|
||||
else
|
||||
folderType = @"Contacts";
|
||||
|
||||
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
|
||||
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
|
||||
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
|
||||
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
|
||||
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
|
||||
|
||||
// remove the folder from device if it doesn't exists or it has not the proper permissions
|
||||
if (!currentFolder ||
|
||||
[sm validatePermission: SoPerm_DeleteObjects
|
||||
onObject: currentFolder
|
||||
inContext: context] ||
|
||||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
||||
onObject: currentFolder
|
||||
inContext: context])
|
||||
{
|
||||
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
|
||||
command_count++;
|
||||
[o destroy];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// We skip personal GCS folders - we always want to synchronize these
|
||||
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
||||
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
||||
continue;
|
||||
|
||||
// Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions
|
||||
if (!currentFolder ||
|
||||
![currentFolder synchronize] ||
|
||||
[sm validatePermission: SoPerm_DeleteObjects
|
||||
onObject: currentFolder
|
||||
inContext: context] ||
|
||||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
||||
onObject: currentFolder
|
||||
inContext: context])
|
||||
{
|
||||
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
|
||||
command_count++;
|
||||
[o destroy];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle addition and changes
|
||||
for (i = 0; i < [allFoldersMetadata count]; i++)
|
||||
@@ -943,6 +972,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
[o save];
|
||||
@@ -953,17 +983,29 @@ static BOOL debugOn = NO;
|
||||
|
||||
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
|
||||
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
|
||||
[folders autorelease];
|
||||
|
||||
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
|
||||
|
||||
// Inside this loop we remove all the folder without write/delete permissions
|
||||
// We remove all the folders that aren't GCS-ones, that we don't want to synchronize and
|
||||
// the ones without write/delete permissions.
|
||||
count = [folders count]-1;
|
||||
for (; count >= 0; count--)
|
||||
{
|
||||
if ([sm validatePermission: SoPerm_DeleteObjects
|
||||
onObject: [folders objectAtIndex: count]
|
||||
currentFolder = [folders objectAtIndex: count];
|
||||
|
||||
// We skip personal GCS folders - we always want to synchronize these
|
||||
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
||||
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
||||
continue;
|
||||
|
||||
if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] ||
|
||||
![currentFolder synchronize] ||
|
||||
[sm validatePermission: SoPerm_DeleteObjects
|
||||
onObject: currentFolder
|
||||
inContext: context] ||
|
||||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
||||
onObject: [folders objectAtIndex: count]
|
||||
onObject: currentFolder
|
||||
inContext: context])
|
||||
{
|
||||
[folders removeObjectAtIndex: count];
|
||||
@@ -971,7 +1013,6 @@ static BOOL debugOn = NO;
|
||||
}
|
||||
|
||||
count = [folders count]-1;
|
||||
NSString *operation;
|
||||
|
||||
for (fi = 0; fi <= count ; fi++)
|
||||
{
|
||||
@@ -1029,6 +1070,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
@@ -1053,6 +1095,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
@@ -1104,17 +1147,18 @@ static BOOL debugOn = NO;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
SOGoMailObject *mailObject;
|
||||
NSMutableArray *a;
|
||||
NSArray *partKeys;
|
||||
int p;
|
||||
|
||||
NSRange r1, r2;
|
||||
|
||||
r1 = [realCollectionId rangeOfString: @"/"];
|
||||
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
|
||||
|
||||
folderName = [realCollectionId substringToIndex: r1.location];
|
||||
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
|
||||
pathToPart = [realCollectionId substringFromIndex: r2.location+1];
|
||||
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
|
||||
[a autorelease];
|
||||
pathToPart = [a lastObject];
|
||||
[a removeLastObject];
|
||||
messageName = [a lastObject];
|
||||
[a removeLastObject];
|
||||
folderName = [a componentsJoinedByString: @"/"];
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
@@ -1263,7 +1307,7 @@ static BOOL debugOn = NO;
|
||||
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId;
|
||||
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
|
||||
NSMutableString *s;
|
||||
NSArray *fetchRequests;
|
||||
id aFetch;
|
||||
@@ -1311,20 +1355,21 @@ static BOOL debugOn = NO;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
SOGoMailObject *mailObject;
|
||||
NSMutableArray *a;
|
||||
|
||||
if ([fileReference length])
|
||||
{
|
||||
// fetch attachment
|
||||
NSRange r1, r2;
|
||||
NSArray *partKeys;
|
||||
int p;
|
||||
|
||||
r1 = [realCollectionId rangeOfString: @"/"];
|
||||
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
|
||||
|
||||
folderName = [realCollectionId substringToIndex: r1.location];
|
||||
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
|
||||
pathToPart = [realCollectionId substringFromIndex: r2.location+1];
|
||||
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
|
||||
[a autorelease];
|
||||
pathToPart = [a lastObject];
|
||||
[a removeLastObject];
|
||||
messageName = [a lastObject];
|
||||
[a removeLastObject];
|
||||
folderName = [a componentsJoinedByString: @"/"];
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
@@ -1340,13 +1385,13 @@ static BOOL debugOn = NO;
|
||||
|
||||
currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context];
|
||||
for (p = 1; p < [partKeys count]; p++)
|
||||
{
|
||||
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
|
||||
}
|
||||
{
|
||||
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
|
||||
}
|
||||
|
||||
[s appendString: @"<Fetch>"];
|
||||
[s appendString: @"<Status>1</Status>"];
|
||||
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">%@</FileReference>", [fileReference stringByEscapingURL]];
|
||||
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">mail/%@/%@/%@</FileReference>", [folderName stringByEscapingURL], messageName, pathToPart];
|
||||
[s appendString: @"<Properties>"];
|
||||
|
||||
[s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]];
|
||||
@@ -1376,6 +1421,13 @@ static BOOL debugOn = NO;
|
||||
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
||||
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
||||
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
||||
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/gg675490%28v=exchg.80%29.aspx
|
||||
// The fetch element is used to request the application data of an item that was truncated in a synchronization response from the server.
|
||||
// The complete item is then returned to the client in a server response.
|
||||
[context setObject: @"8" forKey: @"MIMETruncation"];
|
||||
|
||||
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
||||
|
||||
@@ -1947,7 +1999,7 @@ static BOOL debugOn = NO;
|
||||
NSMutableString *s;
|
||||
id collection;
|
||||
NSData *d;
|
||||
|
||||
NSAutoreleasePool *pool;
|
||||
|
||||
int i, j, heartbeatInterval, defaultInterval, internalInterval, status;
|
||||
|
||||
@@ -1967,6 +2019,9 @@ static BOOL debugOn = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (heartbeatInterval < internalInterval)
|
||||
heartbeatInterval = internalInterval;
|
||||
|
||||
status = 1;
|
||||
}
|
||||
|
||||
@@ -2006,6 +2061,7 @@ static BOOL debugOn = NO;
|
||||
// We enter our loop detection change
|
||||
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
|
||||
{
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
for (j = 0; j < [allFoldersID count]; j++)
|
||||
{
|
||||
collectionId = [allFoldersID objectAtIndex: j];
|
||||
@@ -2030,16 +2086,17 @@ static BOOL debugOn = NO;
|
||||
[foldersWithChanges addObject: collectionId];
|
||||
}
|
||||
}
|
||||
|
||||
DESTROY(pool);
|
||||
|
||||
if ([foldersWithChanges count])
|
||||
{
|
||||
[self logWithFormat: @"Change detected, we push the content."];
|
||||
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
|
||||
status = 2;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
|
||||
sleep(internalInterval);
|
||||
}
|
||||
}
|
||||
@@ -2103,6 +2160,123 @@ static BOOL debugOn = NO;
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
#ifdef HAVE_OPENSSL
|
||||
- (unsigned int) validateCert: (NSString *) theCert
|
||||
{
|
||||
NSData *d;
|
||||
|
||||
const unsigned char *data;
|
||||
X509_STORE_CTX *ctx;
|
||||
X509_LOOKUP *lookup;
|
||||
X509_STORE *store;
|
||||
X509 *cert;
|
||||
|
||||
BOOL success;
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
success = NO;
|
||||
|
||||
d = [theCert dataByDecodingBase64];
|
||||
data = (unsigned char *)[d bytes];
|
||||
len = [d length];
|
||||
|
||||
cert = d2i_X509(NULL, &data, len);
|
||||
if (!cert)
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
|
||||
return 17;
|
||||
}
|
||||
|
||||
store = X509_STORE_new();
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
if (store)
|
||||
{
|
||||
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
||||
if (lookup)
|
||||
{
|
||||
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
|
||||
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
|
||||
if (lookup)
|
||||
{
|
||||
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
|
||||
ERR_clear_error();
|
||||
success = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (store)
|
||||
{
|
||||
X509_STORE_free(store);
|
||||
store = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ctx = X509_STORE_CTX_new();
|
||||
if (!ctx)
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
|
||||
return 17;
|
||||
}
|
||||
|
||||
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
|
||||
X509_STORE_CTX_free(ctx);
|
||||
return 17;
|
||||
}
|
||||
|
||||
rc = X509_verify_cert(ctx);
|
||||
X509_STORE_CTX_free(ctx);
|
||||
X509_free(cert);
|
||||
|
||||
if (rc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
|
||||
return 17;
|
||||
}
|
||||
}
|
||||
#else
|
||||
- (unsigned int) validateCert: (NSString *) theCert
|
||||
{
|
||||
return 17;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSMutableString *s;
|
||||
NSString *cert;
|
||||
NSData *d;
|
||||
|
||||
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
|
||||
|
||||
s = [NSMutableString string];
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<ValidateCert xmlns=\"ValidateCert:\">"];
|
||||
[s appendString: @"<Status>1</Status><Certificate>"];
|
||||
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
|
||||
[s appendString: @"</Certificate></ValidateCert>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// <?xml version="1.0"?>
|
||||
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
||||
@@ -2419,39 +2593,100 @@ static BOOL debugOn = NO;
|
||||
NGMimeMessageParser *parser;
|
||||
NGMimeMessage *message;
|
||||
NSException *error;
|
||||
NSData *data;
|
||||
NGMutableHashMap *map;
|
||||
NGMimeMessage *messageToSend;
|
||||
NGMimeMessageGenerator *generator;
|
||||
NSMutableData *data;
|
||||
NSData *new_from_header;
|
||||
NSDictionary *identity;
|
||||
NSString *fullName, *email;
|
||||
|
||||
const char *bytes;
|
||||
int i, e, len;
|
||||
BOOL found_header;
|
||||
|
||||
// We get the mail's data
|
||||
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
|
||||
data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
|
||||
|
||||
// We extract the recipients
|
||||
parser = [[NGMimeMessageParser alloc] init];
|
||||
message = [parser parsePartFromData: data];
|
||||
RELEASE(parser);
|
||||
|
||||
map = [NGHashMap hashMapWithDictionary: [message headers]];
|
||||
|
||||
identity = [[context activeUser] primaryIdentity];
|
||||
|
||||
fullName = [identity objectForKey: @"fullName"];
|
||||
email = [identity objectForKey: @"email"];
|
||||
|
||||
if ([fullName length])
|
||||
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"];
|
||||
new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", [fullName asQPSubjectString: @"utf-8"], email] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
else
|
||||
[map setObject: email forKey: @"from"];
|
||||
new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
|
||||
bytes = [data bytes];
|
||||
len = [data length];
|
||||
i = 0;
|
||||
found_header = NO;
|
||||
|
||||
[messageToSend setBody: [message body]];
|
||||
// Search for the from-header
|
||||
while (i < len)
|
||||
{
|
||||
if (i == 0 &&
|
||||
(*bytes == 'f' || *bytes == 'F') &&
|
||||
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
|
||||
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
|
||||
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
|
||||
(*(bytes+4) == ':'))
|
||||
{
|
||||
found_header = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
|
||||
(*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
|
||||
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
|
||||
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
|
||||
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
|
||||
(*(bytes+6) == ':'))
|
||||
{
|
||||
found_header = YES;
|
||||
i = i + 2; // \r\n
|
||||
bytes = bytes + 2;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes++;
|
||||
i++;
|
||||
}
|
||||
|
||||
// We search for the first \r\n AFTER the From: header to get the length of the string to replace.
|
||||
e = i;
|
||||
while (e < len)
|
||||
{
|
||||
if ((*bytes == '\r') && (*(bytes+1) == '\n'))
|
||||
{
|
||||
e = e + 2;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes++;
|
||||
e++;
|
||||
}
|
||||
|
||||
// Update/Add the From header in the MIMEBody of the SendMail request.
|
||||
// Any other way to modify the mail body would break s/mime emails.
|
||||
if (found_header)
|
||||
{
|
||||
// Change the From header
|
||||
[data replaceBytesInRange: NSMakeRange(i, (NSUInteger)(e-i))
|
||||
withBytes: [new_from_header bytes]
|
||||
length: [new_from_header length]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add a From header
|
||||
[data replaceBytesInRange: NSMakeRange(0, 0)
|
||||
withBytes: [new_from_header bytes]
|
||||
length: [new_from_header length]];
|
||||
}
|
||||
|
||||
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
|
||||
data = [generator generateMimeFromPart: messageToSend];
|
||||
|
||||
error = [self _sendMail: data
|
||||
recipients: [message allRecipients]
|
||||
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
|
||||
@@ -2627,7 +2862,12 @@ static BOOL debugOn = NO;
|
||||
{
|
||||
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
||||
|
||||
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
|
||||
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] mutableCopy] autorelease];
|
||||
|
||||
// If there is no References: header, initialize it with In-Reply-To.
|
||||
if ([mailObject inReplyTo] && ![references count])
|
||||
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
|
||||
|
||||
if ([references count] > 0)
|
||||
{
|
||||
// If there are more than ten identifiers listed, we eliminate the second one.
|
||||
@@ -2943,9 +3183,25 @@ static BOOL debugOn = NO;
|
||||
options: NSCaseInsensitiveSearch].location == NSNotFound)
|
||||
{
|
||||
NSString *value;
|
||||
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
|
||||
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]];
|
||||
#if GNUSTEP_BASE_MINOR_VERSION < 21
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
||||
locale: nil];
|
||||
#else
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
||||
locale: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSArray arrayWithObjects: @"Jan", @"Feb", @"Mar", @"Apr",
|
||||
@"May", @"Jun", @"Jul", @"Aug",
|
||||
@"Sep", @"Oct", @"Nov", @"Dec", nil],
|
||||
@"NSShortMonthNameArray",
|
||||
[NSArray arrayWithObjects: @"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
|
||||
@"Fri", @"Sat", nil],
|
||||
@"NSShortWeekDayNameArray",
|
||||
nil]];
|
||||
|
||||
#endif
|
||||
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3011,7 +3267,7 @@ static BOOL debugOn = NO;
|
||||
[theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
|
||||
[theResponse setHeader: @"2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
|
||||
|
||||
if (debugOn && [[theResponse content] length])
|
||||
if (debugOn && [[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.wbxml"] && [[theResponse content] length])
|
||||
[self logWithFormat: @"EAS - response for device %@: %@", [context objectForKey: @"DeviceId"], [[[NSString alloc] initWithData: [[theResponse content] wbxml2xml] encoding: NSUTF8StringEncoding] autorelease]];
|
||||
|
||||
RELEASE(context);
|
||||
@@ -3035,8 +3291,7 @@ static BOOL debugOn = NO;
|
||||
return nil;
|
||||
|
||||
urlString = [[user domainDefaults] folderInfoURL];
|
||||
parts = [[urlString componentsSeparatedByString: @"/"]
|
||||
mutableCopy];
|
||||
parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
|
||||
[parts autorelease];
|
||||
if ([parts count] == 5)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSCharacterSet.h>
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
@@ -472,18 +473,46 @@ struct GlobalObjectId {
|
||||
return d;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) _sanitinizeNeeded: (NSArray *) theParts
|
||||
{
|
||||
NSString *encoding, *charset;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < [theParts count]; i++)
|
||||
{
|
||||
encoding = [[theParts objectAtIndex: i ] objectForKey: @"encoding"];
|
||||
charset = [[[theParts objectAtIndex: i ] objectForKey: @"parameterList"] objectForKey: @"charset"];
|
||||
if (encoding && [encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame &&
|
||||
charset && ![charset caseInsensitiveCompare: @"utf-8"] == NSOrderedSame
|
||||
&& ![charset caseInsensitiveCompare: @"us-ascii"] == NSOrderedSame)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ([self _sanitinizeNeeded: [[theParts objectAtIndex: i ] objectForKey: @"parts"]])
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSData *) _preferredBodyDataUsingType: (int) theType
|
||||
mimeSupport: (int) theMimeSupport
|
||||
nativeType: (int *) theNativeType
|
||||
{
|
||||
NSString *type, *subtype, *encoding;
|
||||
NSData *d;
|
||||
BOOL isSMIME, sanitinizeNeeded;
|
||||
|
||||
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
|
||||
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||
|
||||
isSMIME = NO;
|
||||
d = nil;
|
||||
|
||||
// We determine the native type
|
||||
@@ -494,8 +523,14 @@ struct GlobalObjectId {
|
||||
else if ([type isEqualToString: @"multipart"])
|
||||
*theNativeType = 4;
|
||||
|
||||
if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && theMimeSupport > 0)
|
||||
{
|
||||
*theNativeType = 4;
|
||||
isSMIME = YES;
|
||||
}
|
||||
|
||||
// We get the right part based on the preference
|
||||
if (theType == 1 || theType == 2)
|
||||
if ((theType == 1 || theType == 2) && !isSMIME)
|
||||
{
|
||||
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
|
||||
{
|
||||
@@ -536,12 +571,12 @@ struct GlobalObjectId {
|
||||
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
|
||||
}
|
||||
}
|
||||
else if (theType == 4)
|
||||
else if (theType == 4 || isSMIME)
|
||||
{
|
||||
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
|
||||
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
|
||||
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
|
||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
|
||||
// We sanitize the content if the content-transfer-encoding is 8bit and charset is not utf-8 or us-ascii.
|
||||
sanitinizeNeeded = [self _sanitinizeNeeded: [NSArray arrayWithObject: [self bodyStructure]]];
|
||||
|
||||
if (sanitinizeNeeded && !isSMIME)
|
||||
d = [self _sanitizedMIMEMessage];
|
||||
else
|
||||
d = [self content];
|
||||
@@ -572,6 +607,35 @@ struct GlobalObjectId {
|
||||
}
|
||||
|
||||
|
||||
- (NSString *) _truncateContent: (NSString *) theContent
|
||||
limit: (int) theLimit
|
||||
truncated: (int *) wasTruncated
|
||||
{
|
||||
if ([theContent length] > theLimit)
|
||||
{
|
||||
int i, len;
|
||||
|
||||
theContent = [theContent substringToIndex: theLimit];
|
||||
*wasTruncated = 1;
|
||||
|
||||
// We search for the first "space" character starting from the
|
||||
// end and we truncate the string once more. We do this to avoid
|
||||
// truncating the content in the middle of a XML entity
|
||||
len = theLimit-1;
|
||||
|
||||
for (i = len; i >= 0; i--)
|
||||
{
|
||||
if (isspace([theContent characterAtIndex: i]))
|
||||
break;
|
||||
}
|
||||
|
||||
return [theContent substringToIndex: i];
|
||||
}
|
||||
|
||||
*wasTruncated = 0;
|
||||
return theContent;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -656,16 +720,19 @@ struct GlobalObjectId {
|
||||
{
|
||||
NSData *d, *globalObjId;
|
||||
NSArray *attachmentKeys;
|
||||
NSMutableString *s;
|
||||
|
||||
uint32_t v;
|
||||
NSString *p;
|
||||
|
||||
id value;
|
||||
|
||||
iCalCalendar *calendar;
|
||||
NSString *p, *subtype;
|
||||
NSMutableString *s;
|
||||
id value;
|
||||
|
||||
int preferredBodyType, nativeBodyType;
|
||||
int preferredBodyType, mimeSupport, mimeTruncation, nativeBodyType;
|
||||
uint32_t v;
|
||||
|
||||
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||
|
||||
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
||||
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
|
||||
mimeTruncation = [[context objectForKey: @"MIMETruncation"] intValue];
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
@@ -862,7 +929,12 @@ struct GlobalObjectId {
|
||||
else
|
||||
{
|
||||
// MesssageClass and ContentClass
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
|
||||
if ([subtype isEqualToString: @"signed"])
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME.MultipartSigned"];
|
||||
else if ([subtype isEqualToString: @"pkcs7-mime"])
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME"];
|
||||
else
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
|
||||
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
|
||||
}
|
||||
|
||||
@@ -876,10 +948,8 @@ struct GlobalObjectId {
|
||||
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
|
||||
|
||||
// Body - namespace 17
|
||||
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
||||
|
||||
nativeBodyType = 1;
|
||||
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
|
||||
d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
|
||||
|
||||
if (calendar && !d)
|
||||
{
|
||||
@@ -968,9 +1038,50 @@ struct GlobalObjectId {
|
||||
AUTORELEASE(content);
|
||||
|
||||
content = [content activeSyncRepresentationInContext: context];
|
||||
len = [content length];
|
||||
truncated = 0;
|
||||
|
||||
len = [content length];
|
||||
// We handle MIMETruncation
|
||||
switch (mimeTruncation)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
content = @"";
|
||||
len = 0; truncated = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
content = [self _truncateContent: content limit: 4096 truncated: &truncated];
|
||||
len = [content length];
|
||||
break;
|
||||
case 2:
|
||||
content = [self _truncateContent: content limit: 5120 truncated: &truncated];
|
||||
len = [content length];
|
||||
break;
|
||||
case 3:
|
||||
content = [self _truncateContent: content limit: 7168 truncated: &truncated];
|
||||
len = [content length];
|
||||
break;
|
||||
case 4:
|
||||
content = [self _truncateContent: content limit: 10240 truncated: &truncated];
|
||||
len = [content length];
|
||||
break;
|
||||
case 5:
|
||||
content = [self _truncateContent: content limit: 20480 truncated: &truncated];
|
||||
len = [content length];
|
||||
break;
|
||||
case 6:
|
||||
content = [self _truncateContent: content limit: 51200 truncated: &truncated];
|
||||
len = [content length];
|
||||
break;
|
||||
case 7:
|
||||
content = [self _truncateContent: content limit: 102400 truncated: &truncated];
|
||||
len = [content length];
|
||||
break;
|
||||
case 8:
|
||||
default:
|
||||
truncated = 0;
|
||||
}
|
||||
|
||||
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
|
||||
{
|
||||
@@ -978,30 +1089,30 @@ struct GlobalObjectId {
|
||||
[s appendFormat: @"<BodyTruncated xmlns=\"Email:\">%d</BodyTruncated>", truncated];
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
|
||||
// Set the correct type if client requested text/html but we got text/plain
|
||||
// Set the correct type if client requested text/html but we got text/plain.
|
||||
// For s/mime mails type is always 4 if mimeSupport is 1 or 2.
|
||||
if (preferredBodyType == 2 && nativeBodyType == 1)
|
||||
[s appendString: @"<Type>1</Type>"];
|
||||
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
|
||||
[s appendString: @"<Type>4</Type>"];
|
||||
else
|
||||
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
|
||||
|
||||
[s appendFormat: @"<Truncated>%d</Truncated>", truncated];
|
||||
[s appendFormat: @"<Preview></Preview>"];
|
||||
|
||||
if (!truncated)
|
||||
{
|
||||
[s appendFormat: @"<Data>%@</Data>", content];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
|
||||
}
|
||||
[s appendFormat: @"<Data>%@</Data>", content];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Attachments -namespace 16
|
||||
attachmentKeys = [self fetchFileAttachmentKeys];
|
||||
if ([attachmentKeys count])
|
||||
|
||||
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -1071,15 +1182,21 @@ struct GlobalObjectId {
|
||||
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
|
||||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
|
||||
{
|
||||
id value;
|
||||
NSString *reference;
|
||||
|
||||
reference = [[[[self mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
value = [[self mailHeaders] objectForKey: @"references"];
|
||||
|
||||
if ([value isKindOfClass: [NSArray class]])
|
||||
reference = [[[value objectAtIndex: 0] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] objectAtIndex: 0];
|
||||
else
|
||||
reference = [[value componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] objectAtIndex: 0];
|
||||
|
||||
if ([reference length] > 0)
|
||||
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
||||
else if ([self inReplyTo])
|
||||
else if ([[self inReplyTo] length] > 0)
|
||||
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self inReplyTo] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
||||
else if ([self messageId])
|
||||
else if ([[self messageId] length] > 0)
|
||||
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self messageId] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@interface SOGoSyncCacheObject : NSObject
|
||||
{
|
||||
@public
|
||||
id _uid;
|
||||
id _sequence;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Foundation/NSNull.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
static Class NSNullK;
|
||||
|
||||
@implementation SOGoSyncCacheObject
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSNullK = [NSNull class];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
@@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [[self alloc] init];
|
||||
|
||||
[o setUID: theUID];
|
||||
[o setSequence: theSequence];
|
||||
|
||||
[o setUID: [NSNumber numberWithInt: [theUID intValue]]];
|
||||
[o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
|
||||
|
||||
return [o autorelease];
|
||||
}
|
||||
@@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (id) uid
|
||||
{
|
||||
return _uid;
|
||||
return [_uid description];
|
||||
}
|
||||
|
||||
- (void) setUID: (id) theUID
|
||||
@@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (id) sequence
|
||||
{
|
||||
return _sequence;
|
||||
return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
|
||||
}
|
||||
|
||||
- (void) setSequence: (id) theSequence
|
||||
@@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
return [[self uid] compare: [theSyncCacheObject uid]];
|
||||
return [self->_uid compare: theSyncCacheObject->_uid];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
if ([[self sequence] isEqual: [NSNull null]] &&
|
||||
[[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if ([self->_sequence isEqual: [NSNull null]] &&
|
||||
[theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return [self compareUID: theSyncCacheObject];
|
||||
|
||||
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return NSOrderedDescending;
|
||||
|
||||
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return NSOrderedAscending;
|
||||
|
||||
// Must check this here, to avoid comparing NSNull objects
|
||||
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame)
|
||||
if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
|
||||
return [self compareUID: theSyncCacheObject];
|
||||
|
||||
return [[self sequence] compare: [theSyncCacheObject sequence]];
|
||||
return [self->_sequence compare: theSyncCacheObject->_sequence];
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
@@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -58,7 +58,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// don't send negative reminder - not supported
|
||||
if (delta > 0)
|
||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta];
|
||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
|
||||
}
|
||||
|
||||
return s;
|
||||
|
||||
@@ -41,7 +41,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context;
|
||||
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) context;
|
||||
|
||||
- (void) changeParticipationStatus: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) context
|
||||
component: (id) component;
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGObjWeb/WOContext.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
@@ -45,11 +47,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/NSCalendarDate+NGCards.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
|
||||
#import <Appointments/SOGoAppointmentObject.h>
|
||||
|
||||
#include "iCalAlarm+ActiveSync.h"
|
||||
#include "iCalRecurrenceRule+ActiveSync.h"
|
||||
@@ -98,10 +106,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else if ([self created])
|
||||
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
|
||||
// Timezone
|
||||
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
|
||||
|
||||
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
|
||||
if ([self startDate])
|
||||
{
|
||||
if ([self isAllDay])
|
||||
if ([self isAllDay] && !tz)
|
||||
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
|
||||
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
@@ -114,7 +125,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
|
||||
if ([self endDate])
|
||||
{
|
||||
if ([self isAllDay])
|
||||
if ([self isAllDay] && !tz)
|
||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
|
||||
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
@@ -124,20 +135,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
// Timezone
|
||||
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
|
||||
|
||||
if (!tz)
|
||||
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
|
||||
|
||||
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [tz activeSyncRepresentationInContext: context]];
|
||||
if (![self recurrenceId])
|
||||
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [tz activeSyncRepresentationInContext: context]];
|
||||
|
||||
// Organizer and other invitations related properties
|
||||
if ((organizer = [self organizer]))
|
||||
{
|
||||
meetingStatus = 1; // meeting and the user is the meeting organizer.
|
||||
o = [organizer rfc822Email];
|
||||
if ([o length])
|
||||
if (![self recurrenceId] && [o length])
|
||||
{
|
||||
[s appendFormat: @"<Organizer_Email xmlns=\"Calendar:\">%@</Organizer_Email>", o];
|
||||
|
||||
@@ -226,9 +235,37 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
|
||||
|
||||
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx
|
||||
if ([[self uid] length])
|
||||
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
|
||||
|
||||
if (![self recurrenceId] && [[self uid] length])
|
||||
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [[self uid] activeSyncRepresentationInContext: context]];
|
||||
|
||||
// Recurrence rules
|
||||
if ([self isRecurrent])
|
||||
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
|
||||
|
||||
// Comment
|
||||
o = [self comment];
|
||||
//if (![self recurrenceId] && [o length])
|
||||
if ([o length])
|
||||
{
|
||||
// It is very important here to NOT set <Truncated>0</Truncated> in the response,
|
||||
// otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details.
|
||||
o = [o activeSyncRepresentationInContext: context];
|
||||
|
||||
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
|
||||
{
|
||||
[s appendFormat: @"<Body xmlns=\"Calendar:\">%@</Body>", o];
|
||||
[s appendString: @"<BodyTruncated xmlns=\"Calendar:\">0</BodyTruncated>"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
|
||||
[s appendFormat: @"<Data>%@</Data>", o];
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
}
|
||||
|
||||
// Sensitivity
|
||||
if ([[self accessClass] isEqualToString: @"PRIVATE"])
|
||||
v = 2;
|
||||
@@ -251,7 +288,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[s appendFormat: @"</Categories>"];
|
||||
}
|
||||
|
||||
|
||||
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
|
||||
// TODO: improve this to handle more alarm types
|
||||
if ([self hasAlarms])
|
||||
@@ -262,36 +298,79 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[s appendString: [alarm activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
// Recurrence rules
|
||||
// Exceptions
|
||||
if ([self isRecurrent])
|
||||
{
|
||||
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
NSMutableArray *components, *exdates;
|
||||
iCalEvent *current_component;
|
||||
NSString *recurrence_id;
|
||||
|
||||
// Comment
|
||||
o = [self comment];
|
||||
if ([o length])
|
||||
{
|
||||
// It is very important here to NOT set <Truncated>0</Truncated> in the response,
|
||||
// otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details.
|
||||
o = [o activeSyncRepresentationInContext: context];
|
||||
unsigned int count, max, i;
|
||||
|
||||
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
|
||||
components = [NSMutableArray arrayWithArray: [[self parent] events]];
|
||||
max = [components count];
|
||||
|
||||
if (max > 1 || [self hasExceptionDates])
|
||||
{
|
||||
[s appendFormat: @"<Body xmlns=\"Calendar:\">%@</Body>", o];
|
||||
[s appendString: @"<BodyTruncated xmlns=\"Calendar:\">0</BodyTruncated>"];
|
||||
exdates = [NSMutableArray arrayWithArray: [self exceptionDates]];
|
||||
|
||||
[s appendString: @"<Exceptions xmlns=\"Calendar:\">"];
|
||||
|
||||
for (count = 1; count < max; count++)
|
||||
{
|
||||
current_component = [components objectAtIndex: count] ;
|
||||
|
||||
if ([self isAllDay])
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@",
|
||||
[[[current_component recurrenceId] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: -[userTimeZone secondsFromGMTForDate:
|
||||
[current_component recurrenceId]]]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
else
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@", [[current_component recurrenceId]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
[s appendString: @"<Exception>"];
|
||||
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
|
||||
[s appendFormat: @"%@", [current_component activeSyncRepresentationInContext: context]];
|
||||
[s appendString: @"</Exception>"];
|
||||
}
|
||||
|
||||
for (i = 0; i < [exdates count]; i++)
|
||||
{
|
||||
[s appendString: @"<Exception>"];
|
||||
[s appendString: @"<Exception_Deleted>1</Exception_Deleted>"];
|
||||
|
||||
if ([self isAllDay])
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@",
|
||||
[[[[exdates objectAtIndex: i] asCalendarDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: -[userTimeZone secondsFromGMTForDate:
|
||||
[[exdates objectAtIndex: i] asCalendarDate]]]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
else
|
||||
{
|
||||
recurrence_id = [NSString stringWithFormat: @"%@", [[[exdates objectAtIndex: i] asCalendarDate]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
|
||||
[s appendString: @"</Exception>"];
|
||||
}
|
||||
|
||||
[s appendString: @"</Exceptions>"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
|
||||
[s appendFormat: @"<Data>%@</Data>", o];
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
}
|
||||
|
||||
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
|
||||
if (![self recurrenceId])
|
||||
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -337,13 +416,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
iCalDateTime *start, *end;
|
||||
iCalDateTime *start, *end; //, *oldstart;
|
||||
NSCalendarDate *oldstart;
|
||||
NSTimeZone *userTimeZone;
|
||||
iCalTimeZone *tz;
|
||||
id o;
|
||||
int deltasecs;
|
||||
|
||||
BOOL isAllDay;
|
||||
|
||||
|
||||
NSMutableArray *occurences;
|
||||
|
||||
occurences = [NSMutableArray arrayWithArray: [[self parent] events]];
|
||||
|
||||
if ((o = [theValues objectForKey: @"UID"]))
|
||||
[self setUid: o];
|
||||
|
||||
@@ -356,6 +441,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
isAllDay = YES;
|
||||
}
|
||||
else if ([occurences count] && !([theValues objectForKey: @"AllDayEvent"]))
|
||||
{
|
||||
// If the occurence has no AllDay tag use it from master.
|
||||
isAllDay = [[occurences objectAtIndex: 0] isAllDay];
|
||||
}
|
||||
|
||||
//
|
||||
// 0- free, 1- tentative, 2- busy and 3- out of office
|
||||
@@ -422,6 +512,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
o = [o calendarDate];
|
||||
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
|
||||
oldstart = [start dateTime];
|
||||
[start setTimeZone: tz];
|
||||
|
||||
if (isAllDay)
|
||||
@@ -433,6 +524,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
[start setDateTime: o];
|
||||
}
|
||||
|
||||
// Calculate delta if start date has been changed.
|
||||
if (oldstart)
|
||||
deltasecs = [[start dateTime ] timeIntervalSinceDate: oldstart] * -1;
|
||||
else
|
||||
deltasecs = 0;
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"EndTime"]))
|
||||
@@ -491,6 +588,177 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
RELEASE(rule);
|
||||
|
||||
[rule takeActiveSyncValues: o inContext: context];
|
||||
|
||||
// Exceptions
|
||||
if ((o = [theValues objectForKey: @"Exceptions"]) && [o isKindOfClass: [NSArray class]])
|
||||
{
|
||||
NSCalendarDate *recurrenceId, *adjustedRecurrenceId, *currentId;
|
||||
NSMutableArray *exdates, *exceptionstouched;
|
||||
iCalEvent *currentOccurence;
|
||||
NSDictionary *exception;
|
||||
|
||||
unsigned int i, count, max;
|
||||
|
||||
[self removeAllExceptionDates];
|
||||
|
||||
exdates = [NSMutableArray array];
|
||||
exceptionstouched = [NSMutableArray array];
|
||||
|
||||
for (i = 0; i < [o count]; i++)
|
||||
{
|
||||
exception = [o objectAtIndex: i];
|
||||
|
||||
if (![[exception objectForKey: @"Exception_Deleted"] intValue])
|
||||
{
|
||||
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
|
||||
|
||||
if (isAllDay)
|
||||
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
|
||||
|
||||
// When moving the calendar entry (i.e. changing the startDate) iOS and Android sometimes send a value for
|
||||
// Exception_StartTime which is not what we expect.
|
||||
// With this we make sure the recurrenceId is always correct.
|
||||
//
|
||||
// iOS problem - If the master is a no-allday-event but the exception is, an invalid Exception_StartTime is in the request.
|
||||
// e.g. it sends 20150409T040000Z instead of 20150409T060000Z;
|
||||
// This might be a special case since iPhone doesn't allow an allday-exception on a non-allday-event.
|
||||
if (!([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay] minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
|
||||
recurrenceId = [recurrenceId hour:[[start dateTime] hourOfDay] minute:[[start dateTime] minuteOfHour]];
|
||||
|
||||
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
|
||||
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
|
||||
adjustedRecurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0 seconds: deltasecs];
|
||||
|
||||
// search for an existing occurence and update it
|
||||
max = [occurences count];
|
||||
currentOccurence = nil;
|
||||
count = 1;
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count] ;
|
||||
currentId = [currentOccurence recurrenceId];
|
||||
|
||||
if ([currentId compare: adjustedRecurrenceId] == NSOrderedSame)
|
||||
{
|
||||
[exceptionstouched addObject: adjustedRecurrenceId];
|
||||
[currentOccurence takeActiveSyncValues: exception inContext: context];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
count++;
|
||||
currentOccurence = nil;
|
||||
}
|
||||
|
||||
// Create a new occurence if we found none to update.
|
||||
if (!currentOccurence)
|
||||
{
|
||||
iCalDateTime *recid;
|
||||
|
||||
currentOccurence = [self mutableCopy];
|
||||
[currentOccurence removeAllRecurrenceRules];
|
||||
[currentOccurence removeAllExceptionRules];
|
||||
[currentOccurence removeAllExceptionDates];
|
||||
|
||||
[[self parent] addToEvents: currentOccurence];
|
||||
[currentOccurence takeActiveSyncValues: exception inContext: context];
|
||||
|
||||
recid = (iCalDateTime *)[currentOccurence uniqueChildWithTag: @"recurrence-id"];
|
||||
if (isAllDay)
|
||||
[recid setDate: adjustedRecurrenceId];
|
||||
else
|
||||
[recid setDateTime: adjustedRecurrenceId];
|
||||
|
||||
[exceptionstouched addObject: [recid dateTime]];
|
||||
}
|
||||
}
|
||||
else if ([[exception objectForKey: @"Exception_Deleted"] intValue])
|
||||
{
|
||||
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
|
||||
|
||||
if (isAllDay)
|
||||
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0
|
||||
days: 0 hours: 0 minutes: 0
|
||||
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
|
||||
|
||||
// We add only valid exception dates.
|
||||
if ([self doesOccurOnDate: recurrenceId] &&
|
||||
([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay]
|
||||
minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
|
||||
{
|
||||
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
|
||||
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
|
||||
[exdates addObject: [recurrenceId dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: deltasecs]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update exception dates in master event.
|
||||
max = [exdates count];
|
||||
if (max > 0)
|
||||
{
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
if (([exceptionstouched indexOfObject: [exdates objectAtIndex: count]] == NSNotFound))
|
||||
[self addToExceptionDates: [exdates objectAtIndex: count]];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all exceptions included in the request.
|
||||
max = [occurences count];
|
||||
count = 1; // skip the master event
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count] ;
|
||||
currentId = [currentOccurence recurrenceId];
|
||||
|
||||
// Delete all occurences which are not touched (modified/added).
|
||||
if (([exceptionstouched indexOfObject: currentId] == NSNotFound))
|
||||
[[[self parent] children] removeObject: currentOccurence];
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else if ([self isRecurrent])
|
||||
{
|
||||
// We have no exceptions in request but there is a recurrence rule.
|
||||
// Remove all excpetions and exception dates.
|
||||
iCalEvent *currentOccurence;
|
||||
unsigned int count, max;
|
||||
|
||||
[self removeAllExceptionDates];
|
||||
max = [occurences count];
|
||||
count = 1; // skip the master event
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count];
|
||||
[[[self parent] children] removeObject: currentOccurence];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([self isRecurrent])
|
||||
{
|
||||
// We have no recurrence rule in request.
|
||||
// Remove all exceptions, exception dates and recurrence rules.
|
||||
iCalEvent *currentOccurence;
|
||||
unsigned int count, max;
|
||||
|
||||
[self removeAllRecurrenceRules];
|
||||
[self removeAllExceptionRules];
|
||||
[self removeAllExceptionDates];
|
||||
|
||||
max = [occurences count];
|
||||
count = 1; // skip the master event
|
||||
while (count < max)
|
||||
{
|
||||
currentOccurence = [occurences objectAtIndex: count];
|
||||
[[[self parent] children] removeObject: currentOccurence];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Organizer - we don't touch the value unless we're the organizer
|
||||
@@ -498,7 +766,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o]))
|
||||
{
|
||||
iCalPerson *person;
|
||||
|
||||
|
||||
person = [iCalPerson elementWithTag: @"organizer"];
|
||||
[person setEmail: o];
|
||||
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
|
||||
@@ -546,7 +814,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
// Windows phones sens sometimes an empty Attendees tag.
|
||||
// We check it's an array before processing it.
|
||||
if ((o = [theValues objectForKey: @"Attendees"])&& [o isKindOfClass: [NSArray class]])
|
||||
if ((o = [theValues objectForKey: @"Attendees"]) && [o isKindOfClass: [NSArray class]])
|
||||
{
|
||||
NSMutableArray *attendees;
|
||||
NSDictionary *attendee;
|
||||
@@ -595,4 +863,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
}
|
||||
|
||||
- (void) changeParticipationStatus: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) context
|
||||
component: (id) component
|
||||
{
|
||||
NSString *status;
|
||||
id o;
|
||||
|
||||
// See: https://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx
|
||||
//
|
||||
// 0 == Free
|
||||
// 1 == Tentative
|
||||
// 2 == Busy
|
||||
// 3 == Out of Office
|
||||
// 4 == Working elsehwere
|
||||
//
|
||||
if ((o = [theValues objectForKey: @"BusyStatus"]))
|
||||
{
|
||||
// We translate >= 2 into ACCEPTED
|
||||
if ([o intValue] == 0)
|
||||
status = @"NEEDS-ACTION";
|
||||
else if ([o intValue] >= 2)
|
||||
status = @"ACCEPTED";
|
||||
else
|
||||
status = @"TENTATIVE";
|
||||
|
||||
// There's no delegate in EAS
|
||||
[(SOGoAppointmentObject *) component changeParticipationStatus: status
|
||||
withDelegate: nil
|
||||
alarm: nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// Simple reccurrence rule of type "Monthly"
|
||||
type = 2;
|
||||
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
|
||||
[[[self parent] startDate] dayOfMonth]];
|
||||
(int)[[[self parent] startDate] dayOfMonth]];
|
||||
}
|
||||
}
|
||||
else if ([self frequency] == iCalRecurrenceFrequenceYearly)
|
||||
|
||||
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
|
||||
[s appendFormat: @"<Data>%@</Data>", o];
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
|
||||
+5
-4
@@ -57,8 +57,6 @@ ProxyPreserveHost On
|
||||
# Allow from your-cas-host-addr
|
||||
# </Proxy>
|
||||
|
||||
ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
|
||||
|
||||
# Enable to use Microsoft ActiveSync support
|
||||
# Note that you MUST have many sogod workers to use ActiveSync.
|
||||
# See the SOGo Installation and Configuration guide for more details.
|
||||
@@ -67,11 +65,13 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
|
||||
# http://127.0.0.1:20000/SOGo/Microsoft-Server-ActiveSync \
|
||||
# retry=60 connectiontimeout=5 timeout=360
|
||||
|
||||
ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
|
||||
|
||||
<Proxy http://127.0.0.1:20000/SOGo>
|
||||
## adjust the following to your configuration
|
||||
RequestHeader set "x-webobjects-server-port" "443"
|
||||
RequestHeader set "x-webobjects-server-name" "yourhostname"
|
||||
RequestHeader set "x-webobjects-server-url" "https://yourhostname"
|
||||
# RequestHeader set "x-webobjects-server-name" "yourhostname"
|
||||
# RequestHeader set "x-webobjects-server-url" "https://yourhostname"
|
||||
|
||||
## When using proxy-side autentication, you need to uncomment and
|
||||
## adjust the following line:
|
||||
@@ -90,5 +90,6 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
|
||||
<IfModule rewrite_module>
|
||||
RewriteEngine On
|
||||
RewriteRule ^/.well-known/caldav/?$ /SOGo/dav [R=301]
|
||||
RewriteRule ^/.well-known/carddav/?$ /SOGo/dav [R=301]
|
||||
</IfModule>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
Installation and Configuration Guide
|
||||
====================================
|
||||
:toc: left
|
||||
:icons: font
|
||||
|
||||
////
|
||||
|
||||
@@ -54,7 +56,7 @@ at http://www.sogo.nu/
|
||||
Architecture and Compatibility
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
image::images/architecture.png[System Architecture]
|
||||
image::images/architecture.png[System Architecture, 400, 964]
|
||||
|
||||
Standard protocols such as CalDAV, CardDAV, GroupDAV, HTTP, IMAP and
|
||||
SMTP are used to communicate with the SOGo platform or its
|
||||
@@ -170,7 +172,7 @@ supported by SOGo:
|
||||
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
|
||||
* Community ENTerprise Operating System (CentOS) 5, 6 and 7
|
||||
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie)
|
||||
* Ubuntu 10.04 (Lucid) to 14.04 (Trusty)
|
||||
* Ubuntu 12.04 (Precise) to 14.04 (Trusty)
|
||||
|
||||
Make sure the required components are started automatically at boot time
|
||||
and that they are running before proceeding with the SOGo configuration.
|
||||
@@ -179,7 +181,7 @@ standard distribution. For example, if you are using Red Hat Enterprise
|
||||
Linux 5, you have to be subscribed to the Red Hat Network before
|
||||
continuing with the SOGo software installation.
|
||||
|
||||
This document covers the installation of SOGo under RHEL 6.
|
||||
NOTE: This document covers the installation of SOGo under RHEL 6.
|
||||
|
||||
For installation instructions on Debian and Ubuntu, please refer
|
||||
directly to the SOGo website at http://www.sogo.nu/.
|
||||
@@ -294,7 +296,7 @@ associated to a domain is limited to access only the users data from the
|
||||
same domain. Consequently, the configuration parameters of SOGo are
|
||||
defined on three levels:
|
||||
|
||||
image::images/preferences-hierarchy.png[Preferences Hierarchy]
|
||||
image::images/preferences-hierarchy.png[Preferences Hierarchy, 400, 400]
|
||||
|
||||
Each level inherits the preferences of the parent level. Therefore,
|
||||
domain preferences define the defaults values of the user preferences,
|
||||
@@ -1125,7 +1127,7 @@ fax number, one could map it to the _facsimiletelephonenumber_ attribute
|
||||
like this:
|
||||
|
||||
----
|
||||
mapping = \{
|
||||
mapping = {
|
||||
facsimiletelephonenumber = ("fax", "facsimiletelephonenumber");
|
||||
};
|
||||
----
|
||||
@@ -1628,6 +1630,16 @@ Defaults to `Trash` when unset.
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Trash`.
|
||||
|
||||
|U |SOGoJunkFolderName
|
||||
|Parameter used to set the IMAP folder name used to store junk
|
||||
messages.
|
||||
|
||||
Defaults to `Junk` when unset.
|
||||
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Junk`. Also see the SOGoMailJunkSettings for
|
||||
more options regarding junk/not-junk actions.
|
||||
|
||||
|D |SOGoIMAPCASServiceName
|
||||
|Parameter used to set the CAS service name (URL) of the imap service.
|
||||
This is useful if SOGo is connecting to the IMAP service through a
|
||||
@@ -2082,7 +2094,21 @@ Defaults to `%{FolderName} (%{UserName} <%{Email}>)` when unset.
|
||||
the template named `UIxAdditionalPreferences.wox`. This template should
|
||||
be put under `~sogo/GNUstep/Library/SOGo/Templates/PreferencesUI/`.
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|D |SOGoMailJunkSettings
|
||||
|Parameter used to enable email junk settings. The value is a dictionary
|
||||
and the follow keys are supported: `vendor` (which must be set to "generic"
|
||||
for now), `junkEmailAddress` which sets the email address to whom SOGo will
|
||||
send junk mails to, `notJunkEmailAddress` which sets the email address to
|
||||
whome SOGo will send non-junk mails to and `limit`, which is an integer value
|
||||
and sets the maximum number of mails that will be attached to a
|
||||
junk/not junk report sent by SOGo. Example: `SOGoMailJunkSettings = {
|
||||
vendor = "generic"; junkEmailAddress = "spam@foo.com";
|
||||
notJunkEmailAddress = "ham@foo.com"; limit = 10;
|
||||
};`
|
||||
|
||||
|D |SOGoMailKeepDraftsAfterSend
|
||||
|Parameter used to keep mails in the drafts folder once they have been
|
||||
sent by SOGo. Defaults to `NO` when unset.
|
||||
|=======================================================================
|
||||
|
||||
SOGo Configuration Summary
|
||||
@@ -2111,6 +2137,7 @@ like this:
|
||||
SOGoDraftsFolderName = Drafts;
|
||||
SOGoSentFolderName = Sent;
|
||||
SOGoTrashFolderName = Trash;
|
||||
SOGoJunkFolderName = Junk;
|
||||
SOGoMailingMechanism = smtp;
|
||||
SOGoSMTPServer = 127.0.0.1;
|
||||
SOGoUserSources = (
|
||||
|
||||
@@ -388,7 +388,7 @@ Run the following commands as root:
|
||||
|
||||
----
|
||||
samba-tool domain provision --realm=example.com \
|
||||
--domain=OPENCHANGE \
|
||||
--domain=EXAMPLE \
|
||||
--adminpass='%1OpenChange' \
|
||||
--server-role='domain controller'
|
||||
|
||||
@@ -818,9 +818,8 @@ can be ignored for now. This feature is currently not supported.
|
||||
remove any data associated with the user from the SOGo server and
|
||||
recreate a Microsoft Outlook profile.
|
||||
To remove any data associated to a user, use
|
||||
the `openchange_user_cleanup` script distributed with SOGo. The script
|
||||
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
|
||||
RHEL).
|
||||
the `openchange_user_cleanup` script distributed with OpenChange. The script
|
||||
can be found in `/usr/share/openchange/`.
|
||||
To reset a user, run the script as root:
|
||||
`openchange_user_cleanup username`. See the usage output for additional options.
|
||||
* The "Out of Office Assistant" will not currently work. This feature
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- TODO have the build system take care of this -->
|
||||
<releaseinfo>Version 2.3.2 - September 2015</releaseinfo>
|
||||
<subtitle>for version 2.3.</subtitle>
|
||||
<date>2015-09-16</date>
|
||||
<releaseinfo>Version 2.3.9 - March 2016</releaseinfo>
|
||||
<subtitle>for version 2.3.9</subtitle>
|
||||
<date>2016-03-16</date>
|
||||
|
||||
<legalnotice>
|
||||
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
Authors:
|
||||
- Inverse inc. <info@inverse.ca>
|
||||
|
||||
Copyright (C) 2008-2015 Inverse inc.
|
||||
Copyright (C) 2008-2016 Inverse inc.
|
||||
License: GFDL 1.2 or later. http://www.gnu.org/licenses/fdl.html
|
||||
|
||||
////
|
||||
|
||||
// TODO have the build system take care of this
|
||||
|
||||
:release_version: 2.3.2
|
||||
:release_version: 2.3.9
|
||||
|
||||
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
|
||||
+1
-1
@@ -267,7 +267,7 @@
|
||||
NSObject <DOMNodeList> *list;
|
||||
NSObject <DOMNode> *valueNode;
|
||||
NSArray *elements;
|
||||
NSString *property, *match;
|
||||
NSString *property=nil, *match=nil;
|
||||
|
||||
list = [searchElement getElementsByTagName: @"prop"];
|
||||
if ([list length])
|
||||
|
||||
@@ -1,3 +1,153 @@
|
||||
2.3.9 (2016-03-16)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- you can now limit the file upload size using the WOMaxUploadSize
|
||||
configuration parameter (integer value in kilobytes) (#3510, #3135)
|
||||
|
||||
Enhancements
|
||||
- allow resources to prevent invitations (#3410)
|
||||
- now support EAS MIME truncation
|
||||
- [web] added Lithuanan (lt) translation - thanks to Mantas Liobė
|
||||
|
||||
Bug fixes
|
||||
- allow EAS attachments get on 2nd-level mailboxes (#3505)
|
||||
- fixed EAS bday shift (#3518)
|
||||
- prefer SOGoRefreshViewCheck to SOGoMailMessageCheck (#3465)
|
||||
- properly unfold long mail headers (#3152)
|
||||
|
||||
2.3.8 (2016-02-05)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- updated CKEditor to version 4.5.7
|
||||
|
||||
Bug fixes
|
||||
- correctly encode filename of attachments over EAS (#3491)
|
||||
- correctly encode square brackets for IMAP folder names (#3321)
|
||||
- add shared/public namespaces in the list or returned folders
|
||||
|
||||
2.3.7 (2016-01-25)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- new junk/not junk capability with generic SMTP integration
|
||||
|
||||
Enhancements
|
||||
- newly created folders using EAS are always sync'ed by default (#3454)
|
||||
- added Croatian (hr_HR) translation - thanks to Jens Riecken
|
||||
|
||||
Bug fixes
|
||||
- now always generate invitation updates when using EAS
|
||||
- rewrote the string sanitization to be 32-bit Unicode safe
|
||||
- do not try to decode non-wbxml responses for debug output (#3444)
|
||||
|
||||
2.3.6 (2016-01-18)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- now able to sync only default mail folders when using EAS
|
||||
|
||||
Enhancements
|
||||
- unit testing for RTFHandler
|
||||
- JUnit output for sogo-tests
|
||||
|
||||
Bug fixes
|
||||
- don't unescape twice mail folder names (#3423)
|
||||
- don't consider mobile Outlook EAS clients as DAV ones (#3431)
|
||||
- we now follow 301 redirects when fetching ICS calendars
|
||||
- when deleting an event using EAS, properly invoke the auto-scheduling code
|
||||
- do not include failure attachments (really long filenames)
|
||||
- fix encoding of email subjects with non-ASCII characters
|
||||
- fix appointment notification mails using SOGoEnableDomainBasedUID configuration
|
||||
- fix shifts in event times on Outlook
|
||||
|
||||
2.3.5 (2016-01-05)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- return an error to openchange if mail message delivery fails
|
||||
- return the requested elements on complex requests from Outlook when downloading changes
|
||||
- user sources can be loaded dynamically
|
||||
- unify user sources API
|
||||
- updated Russian translation (#3383)
|
||||
|
||||
Bug fixes
|
||||
- properly compute the last week number for the year (#1010)
|
||||
- share calendar, tasks and contacts folders in Outlook 2013 with editor permissions
|
||||
- priorize filename in Content-Disposition against name in Content-Type to get the filename of an attachment in mail
|
||||
- request all contacts when there is no filter in Contacts menu in Webmail
|
||||
- personal contacts working properly on Outlook
|
||||
- fixes on RTF parsing used by event/contact description and mail as RTF to read non-ASCII characters:
|
||||
better parsing of font table, when using a font, switch to its character set, correct parsing of
|
||||
escaped characters and Unicode character command word support for unicode characters greater than 32767
|
||||
- no crash resolving recipients after reconnecting LDAP connection
|
||||
- avoid creation of phantom contacts in SOGo from distribution list synced from Outlook.
|
||||
- accepted & updated event names are now shown correctly in Outlook
|
||||
- provide safe guards in mail and calendar to avoid exceptions while syncing
|
||||
|
||||
2.3.4 (2015-12-15)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- initial support for EAS calendar exceptions
|
||||
|
||||
Enhancements
|
||||
- limit the maximum width of toolbar buttons (#3381)
|
||||
- updated CKEditor to version 4.5.6
|
||||
|
||||
Bug fixes
|
||||
- JavaScript exception when printing events from calendars with no assigned color (#3203)
|
||||
- EAS fix for wrong charset being used (#3392)
|
||||
- EAS fix on qp-encoded subjects (#3390)
|
||||
- correctly handle all-day event exceptions when the master event changes
|
||||
- prevent characters in calendar component UID causing issues during import process
|
||||
- avoid duplicating attendees when accepting event using a different identity over CalDAV
|
||||
|
||||
2.3.3a (2015-11-18)
|
||||
-------------------
|
||||
|
||||
Bug fixes
|
||||
- expanded mail folders list is not saved (#3386)
|
||||
- cleanup translations
|
||||
|
||||
2.3.3 (2015-11-11)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- initial S/MIME support for EAS (#3327)
|
||||
- now possible to choose which folders to sync over EAS
|
||||
|
||||
Enhancements
|
||||
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS
|
||||
- support for ghosted elements on contacts over EAS
|
||||
- added Macedonian (mk_MK) translation - thanks to Miroslav Jovanovic
|
||||
- added Portuguese (pt) translation - thanks to Eduardo Crispim
|
||||
|
||||
Bug fixes
|
||||
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
|
||||
- correctly handle the References header over EAS (#3365)
|
||||
- make sure English is always used when generating Date headers using EAS (#3356)
|
||||
- don't escape quoted strings during versit generation
|
||||
- we now return all cards when we receive an empty addressbook-query REPORT
|
||||
- avoid crash when replying to a mail with no recipients (#3359)
|
||||
- inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271)
|
||||
- prevent postal address showing on single line over EAS (#2614)
|
||||
- display missing events when printing working hours only
|
||||
- fix corner case making server crash when syncing hard deleted messages when clear offline items was set up (Zentyal)
|
||||
- avoid infinite Outlook client loops trying to set read flag when it is already set (Zentyal)
|
||||
- avoid crashing when calendar metadata is missing in the cache (Zentyal)
|
||||
- fix recurrence pattern event corner case created by Mozilla Thunderbird which made server crash (Zentyal)
|
||||
- fix corner case that removes attachments on sending messages from Outlook (Zentyal)
|
||||
- freebusy on web interface works again in multidomain environments (Zentyal)
|
||||
- fix double creation of folders in Outlook when the folder name starts with a digit (Zentyal)
|
||||
- avoid crashing Outlook after setting a custom view in a calendar folder (Zentyal)
|
||||
- handle emails having an attachment as their content
|
||||
- fixed JavaScript syntax error in attendees editor
|
||||
- fixed wrong comparison of meta vs. META tag in HTML mails
|
||||
- fixed popup menu position when moved to the left (#3381)
|
||||
- fixed dialog position when at the bottom of the window (#2646, #3378)
|
||||
|
||||
2.3.2 (2015-09-16)
|
||||
------------------
|
||||
|
||||
|
||||
@@ -145,7 +145,8 @@ $(DBMSGREADER_TOOL)_LIB_DIRS += \
|
||||
-L../SoObjects/SOGo/SOGo.framework/sogo -lSOGo \
|
||||
-L../SOPE/GDLContentStore/obj/ -lGDLContentStore \
|
||||
-L../SOPE/NGCards/obj/ -lNGCards \
|
||||
-lNGObjWeb
|
||||
-lNGObjWeb \
|
||||
$(LIBMAPI_LIBS)
|
||||
|
||||
TEST_TOOL_NAME += $(PLREADER_TOOL) $(DBMSGREADER_TOOL)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#define MAPISTORECALENDARWRAPPER_H
|
||||
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
|
||||
#import "MAPIStoreObjectProxy.h"
|
||||
@@ -42,7 +43,7 @@
|
||||
iCalCalendar *calendar;
|
||||
iCalEvent *firstEvent;
|
||||
iCalEvent *event;
|
||||
NSTimeZone *timeZone;
|
||||
iCalTimeZone *timeZone;
|
||||
SOGoUser *user;
|
||||
NSString *senderEmail;
|
||||
NSData *globalObjectId;
|
||||
@@ -57,12 +58,10 @@
|
||||
+ (id) wrapperWithICalEvent: (iCalEvent *) newEvent
|
||||
andUser: (SOGoUser *) newUser
|
||||
andSenderEmail: (NSString *) newSenderEmail
|
||||
inTimeZone: (NSTimeZone *) newTimeZone
|
||||
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo;
|
||||
- (id) initWithICalEvent: (iCalEvent *) newEvent
|
||||
andUser: (SOGoUser *) newUser
|
||||
andSenderEmail: (NSString *) newSenderEmail
|
||||
inTimeZone: (NSTimeZone *) newTimeZone
|
||||
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo;
|
||||
|
||||
/* getters */
|
||||
|
||||
@@ -37,7 +37,9 @@
|
||||
#import <NGCards/iCalEventChanges.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
#import <NGCards/iCalTimeZonePeriod.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
|
||||
@@ -80,7 +82,6 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
+ (id) wrapperWithICalEvent: (iCalEvent *) newEvent
|
||||
andUser: (SOGoUser *) newUser
|
||||
andSenderEmail: (NSString *) newSenderEmail
|
||||
inTimeZone: (NSTimeZone *) newTimeZone
|
||||
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo
|
||||
{
|
||||
MAPIStoreAppointmentWrapper *wrapper;
|
||||
@@ -88,7 +89,6 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
wrapper = [[self alloc] initWithICalEvent: newEvent
|
||||
andUser: newUser
|
||||
andSenderEmail: newSenderEmail
|
||||
inTimeZone: newTimeZone
|
||||
withConnectionInfo: newConnInfo];
|
||||
[wrapper autorelease];
|
||||
|
||||
@@ -182,10 +182,10 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
- (id) initWithICalEvent: (iCalEvent *) newEvent
|
||||
andUser: (SOGoUser *) newUser
|
||||
andSenderEmail: (NSString *) newSenderEmail
|
||||
inTimeZone: (NSTimeZone *) newTimeZone
|
||||
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo
|
||||
{
|
||||
NSArray *events;
|
||||
iCalTimeZone *tz;
|
||||
|
||||
if ((self = [self init]))
|
||||
{
|
||||
@@ -194,9 +194,20 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
event = newEvent;
|
||||
events = [calendar events];
|
||||
firstEvent = [events objectAtIndex: 0];
|
||||
ASSIGN (timeZone, newTimeZone);
|
||||
ASSIGN (user, newUser);
|
||||
ASSIGN (senderEmail, newSenderEmail);
|
||||
/* If newEvent comes from the client, we set its time zone in
|
||||
updateFromMAPIProperties. If it is not present, we use the
|
||||
time zone of the user */
|
||||
tz = (iCalTimeZone *) [calendar firstChildWithTag: @"vtimezone"];
|
||||
if (!tz)
|
||||
{
|
||||
tz = [iCalTimeZone timeZoneForName: [[[user userDefaults] timeZone] name]];
|
||||
if (!tz)
|
||||
[self logWithFormat: @"no time zone could be set"];
|
||||
}
|
||||
ASSIGN (timeZone, tz);
|
||||
|
||||
[self _setupITIPContext];
|
||||
}
|
||||
|
||||
@@ -266,7 +277,7 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
{
|
||||
username = [contactInfos objectForKey: @"sAMAccountName"];
|
||||
recipient->username = [username asUnicodeInMemCtx: msgData];
|
||||
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
|
||||
entryId = MAPIStoreInternalEntryId (connInfo, username);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -367,7 +378,7 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
{
|
||||
username = [contactInfos objectForKey: @"sAMAccountName"];
|
||||
recipient->username = [username asUnicodeInMemCtx: msgData];
|
||||
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
|
||||
entryId = MAPIStoreInternalEntryId (connInfo, username);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -721,22 +732,15 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSCalendarDate *dateValue;
|
||||
NSInteger offset;
|
||||
|
||||
// if ([event isRecurrent])
|
||||
// dateValue = [event firstRecurrenceStartDate];
|
||||
// else
|
||||
dateValue = [event startDate];
|
||||
if ([event isAllDay])
|
||||
{
|
||||
offset = -[timeZone secondsFromGMTForDate: dateValue];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
}
|
||||
[dateValue setTimeZone: utcTZ];
|
||||
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
|
||||
*data = [dateValue asFileTimeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -749,22 +753,14 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
exceptions, where it is the normal start date for the day of the
|
||||
exception. */
|
||||
NSCalendarDate *dateValue;
|
||||
NSInteger offset;
|
||||
|
||||
dateValue = [event recurrenceId];
|
||||
if (!dateValue)
|
||||
dateValue = [event startDate];
|
||||
[dateValue setTimeZone: timeZone];
|
||||
if ([event isAllDay])
|
||||
{
|
||||
offset = -[timeZone secondsFromGMTForDate: dateValue];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
}
|
||||
[dateValue setTimeZone: utcTZ];
|
||||
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
|
||||
*data = [dateValue asFileTimeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -772,19 +768,12 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSCalendarDate *dateValue;
|
||||
NSInteger offset;
|
||||
|
||||
dateValue = [firstEvent startDate];
|
||||
if ([firstEvent isAllDay])
|
||||
{
|
||||
offset = -[timeZone secondsFromGMTForDate: dateValue];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
}
|
||||
[dateValue setTimeZone: utcTZ];
|
||||
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
|
||||
*data = [dateValue asFileTimeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -804,8 +793,8 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
month: [start monthOfYear]
|
||||
day: [start dayOfMonth]
|
||||
hour: 0 minute: 0 second: 0
|
||||
timeZone: timeZone];
|
||||
[dateValue setTimeZone: utcTZ];
|
||||
timeZone: utcTZ];
|
||||
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
|
||||
*data = [dateValue asFileTimeInMemCtx: memCtx];
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
@@ -829,7 +818,7 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
dateValue = [event startDate];
|
||||
offset = [event durationAsTimeInterval];
|
||||
if ([event isAllDay])
|
||||
offset -= [timeZone secondsFromGMTForDate: dateValue];
|
||||
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
@@ -847,15 +836,14 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
dateValue = [event recurrenceId];
|
||||
if (!dateValue)
|
||||
dateValue = [event startDate];
|
||||
[dateValue setTimeZone: timeZone];
|
||||
offset = [firstEvent durationAsTimeInterval];
|
||||
if ([firstEvent isAllDay])
|
||||
offset -= [timeZone secondsFromGMTForDate: dateValue];
|
||||
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
*data = [dateValue asFileTimeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -871,7 +859,7 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
dateValue = [firstEvent startDate];
|
||||
offset = [firstEvent durationAsTimeInterval];
|
||||
if ([event isAllDay])
|
||||
offset -= [timeZone secondsFromGMTForDate: dateValue];
|
||||
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
@@ -885,23 +873,14 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
{
|
||||
enum mapistore_error rc;
|
||||
NSCalendarDate *dateValue;
|
||||
NSInteger offset;
|
||||
iCalRecurrenceRule *rrule;
|
||||
|
||||
if ([event isRecurrent])
|
||||
{
|
||||
rrule = [[event recurrenceRules] objectAtIndex: 0];
|
||||
dateValue = [rrule untilDate];
|
||||
if (dateValue)
|
||||
{
|
||||
if ([event isAllDay])
|
||||
offset = -[timeZone secondsFromGMTForDate: dateValue];
|
||||
else
|
||||
offset = 0;
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
}
|
||||
if (dateValue && [event isAllDay])
|
||||
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
|
||||
else
|
||||
dateValue = [NSCalendarDate dateWithYear: 4500 month: 8 day: 31
|
||||
hour: 23 minute: 59 second: 00
|
||||
@@ -932,7 +911,7 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
if (contactInfos)
|
||||
{
|
||||
username = [contactInfos objectForKey: @"sAMAccountName"];
|
||||
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
|
||||
entryId = MAPIStoreInternalEntryId (connInfo, username);
|
||||
}
|
||||
else
|
||||
entryId = MAPIStoreExternalEntryId (cn, email);
|
||||
@@ -1338,21 +1317,14 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
{
|
||||
enum mapistore_error rc;
|
||||
NSCalendarDate *dateValue;
|
||||
NSInteger offset;
|
||||
|
||||
dateValue = [event recurrenceId];
|
||||
if (dateValue)
|
||||
{
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
|
||||
|
||||
if ([event isAllDay])
|
||||
{
|
||||
offset = -[timeZone secondsFromGMTForDate: dateValue];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
}
|
||||
[dateValue setTimeZone: utcTZ];
|
||||
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
|
||||
*data = [dateValue asFileTimeInMemCtx: memCtx];
|
||||
}
|
||||
else
|
||||
@@ -1377,7 +1349,6 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
iCalEventChanges *changes;
|
||||
NSArray *changedProperties;
|
||||
NSCalendarDate *dateValue;
|
||||
NSInteger offset;
|
||||
|
||||
changes = [iCalEventChanges changesFromEvent: event toEvent: exceptionEvent];
|
||||
|
||||
@@ -1385,28 +1356,17 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
memset (extendedException, 0, sizeof (struct ExtendedException));
|
||||
extendedException->ChangeHighlight.Size = sizeof (uint32_t);
|
||||
|
||||
dateValue = [exceptionEvent startDate];
|
||||
offset = [timeZone secondsFromGMTForDate: dateValue];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
dateValue = [timeZone computedDateForDate: [exceptionEvent startDate]];
|
||||
exceptionInfo->StartDateTime = [dateValue asMinutesSince1601];
|
||||
extendedException->ChangeHighlight.Value = BIT_CH_START;
|
||||
extendedException->StartDateTime = exceptionInfo->StartDateTime;
|
||||
|
||||
dateValue = [exceptionEvent endDate];
|
||||
offset = [timeZone secondsFromGMTForDate: dateValue];
|
||||
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
dateValue = [timeZone computedDateForDate: [exceptionEvent endDate]];
|
||||
exceptionInfo->EndDateTime = [dateValue asMinutesSince1601];
|
||||
extendedException->ChangeHighlight.Value |= BIT_CH_END;
|
||||
extendedException->EndDateTime = exceptionInfo->EndDateTime;
|
||||
|
||||
dateValue = [[exceptionEvent recurrenceId]
|
||||
dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
dateValue = [timeZone computedDateForDate: [exceptionEvent recurrenceId]];
|
||||
exceptionInfo->OriginalStartDate = [dateValue asMinutesSince1601];
|
||||
extendedException->OriginalStartDate = exceptionInfo->OriginalStartDate;
|
||||
|
||||
@@ -1464,7 +1424,6 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
arp = talloc_zero (NULL, struct AppointmentRecurrencePattern);
|
||||
[rule fillRecurrencePattern: &arp->RecurrencePattern
|
||||
withEvent: event
|
||||
inTimeZone: timeZone
|
||||
inMemCtx: arp];
|
||||
arp->ReaderVersion2 = 0x00003006;
|
||||
arp->WriterVersion2 = 0x00003008; /* 0x3008 for compatibility with
|
||||
@@ -1475,7 +1434,7 @@ static NSCharacterSet *hexCharacterSet = nil;
|
||||
fields are relative to midnight of those days ([MS-OXOCAL] 2.2.1.44.5),
|
||||
so no time zone adjustment is needed */
|
||||
if (![event isAllDay])
|
||||
[firstStartDate setTimeZone: timeZone];
|
||||
firstStartDate = [timeZone computedDateForDate: firstStartDate];
|
||||
startMinutes = ([firstStartDate hourOfDay] * 60
|
||||
+ [firstStartDate minuteOfHour]);
|
||||
arp->StartTimeOffset = startMinutes;
|
||||
@@ -1701,15 +1660,16 @@ ReservedBlockEE2Size: 00 00 00 00
|
||||
fromDate: (NSCalendarDate *) instanceDate;
|
||||
{
|
||||
uint16_t year;
|
||||
NSCalendarDate *dateValue;
|
||||
|
||||
if (instanceDate)
|
||||
{
|
||||
[instanceDate setTimeZone: timeZone];
|
||||
year = [instanceDate yearOfCommonEra];
|
||||
dateValue = [timeZone computedDateForDate: instanceDate];
|
||||
year = [dateValue yearOfCommonEra];
|
||||
newGlobalId->YH = year >> 8;
|
||||
newGlobalId->YL = year & 0xff;
|
||||
newGlobalId->Month = [instanceDate monthOfYear];
|
||||
newGlobalId->D = [instanceDate dayOfMonth];
|
||||
newGlobalId->Month = [dateValue monthOfYear];
|
||||
newGlobalId->D = [dateValue dayOfMonth];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1974,7 +1934,6 @@ ReservedBlockEE2Size: 00 00 00 00
|
||||
if (alarm)
|
||||
{
|
||||
alarmDate = [alarm nextAlarmDate];
|
||||
[alarmDate setTimeZone: utcTZ];
|
||||
*data = [alarmDate asFileTimeInMemCtx: memCtx];
|
||||
}
|
||||
else
|
||||
@@ -2036,8 +1995,7 @@ ReservedBlockEE2Size: 00 00 00 00
|
||||
enum mapistore_error rc;
|
||||
NSString *tzid;
|
||||
|
||||
tzid = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"]
|
||||
value: 0 ofAttribute: @"tzid"];
|
||||
tzid = [timeZone tzId];
|
||||
if ([tzid length] > 0)
|
||||
{
|
||||
*data = [tzid asUnicodeInMemCtx: memCtx];
|
||||
@@ -2053,16 +2011,9 @@ ReservedBlockEE2Size: 00 00 00 00
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
enum mapistore_error rc;
|
||||
iCalTimeZone *icalTZ;
|
||||
|
||||
icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone];
|
||||
if (icalTZ)
|
||||
{
|
||||
*data = [icalTZ asTimeZoneStructInMemCtx: memCtx];
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
rc = MAPISTORE_ERR_NOT_FOUND;
|
||||
*data = [timeZone asTimeZoneStructInMemCtx: memCtx];
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -2071,24 +2022,16 @@ ReservedBlockEE2Size: 00 00 00 00
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
enum mapistore_error rc;
|
||||
iCalTimeZone *icalTZ;
|
||||
|
||||
/* [MS-OXOCAL] 3.1.5.5.1: This property is used in floating (all-day) events,
|
||||
specified in floating time, to convert the start date from UTC to the user's
|
||||
time zone */
|
||||
if ([event isAllDay])
|
||||
icalTZ = [iCalTimeZone timeZoneForName: [timeZone timeZoneName]];
|
||||
else if ([event isRecurrent])
|
||||
icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone];
|
||||
else
|
||||
icalTZ = nil;
|
||||
|
||||
if (icalTZ)
|
||||
if ([event isAllDay] | [event isRecurrent])
|
||||
{
|
||||
/* [MS-OXOCAL] 2.2.1.42: This property can only have the E flag set in the
|
||||
TimeZoneDefinition struct */
|
||||
*data = [icalTZ asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG
|
||||
inMemCtx: memCtx];
|
||||
*data = [timeZone asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG
|
||||
inMemCtx: memCtx];
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
forURL: (NSURL *) server
|
||||
forceRenew: (BOOL) renew;
|
||||
|
||||
- (NSString *) passwordInContext: (WOContext *) context;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
#endif /* MAPISTOREAUTHENTICATOR_H */
|
||||
@@ -72,4 +72,8 @@
|
||||
return imapPassword;
|
||||
}
|
||||
|
||||
- (NSString *) passwordInContext: (WOContext *) context
|
||||
{
|
||||
return password;
|
||||
}
|
||||
@end
|
||||
@@ -58,7 +58,6 @@
|
||||
wrapperWithICalEvent: [newContainer event]
|
||||
andUser: [userContext sogoUser]
|
||||
andSenderEmail: nil
|
||||
inTimeZone: [userContext timeZone]
|
||||
withConnectionInfo: [context connectionInfo]];
|
||||
[self addProxy: appointmentWrapper];
|
||||
}
|
||||
|
||||
@@ -77,6 +77,17 @@
|
||||
|
||||
- (NSArray *) rolesForExchangeRights: (uint32_t) rights
|
||||
{
|
||||
/* Limitations
|
||||
|
||||
Following rights are not supported by SOGo specifically:
|
||||
|
||||
- DeleteOwned : Delete only own objects
|
||||
- EditOwned : Edit only own objects
|
||||
- CreateSubfolders: No calendar subfolders
|
||||
- FolderOwner: No sharing folder ownership?
|
||||
- FolderContact: No support to store this information
|
||||
- FolderVisible: It is inferred by other rights when extracting
|
||||
*/
|
||||
NSMutableArray *roles;
|
||||
|
||||
roles = [NSMutableArray arrayWithCapacity: 6];
|
||||
@@ -117,11 +128,11 @@
|
||||
if ([roles containsObject: SOGoRole_ObjectCreator])
|
||||
rights |= RightsCreateItems;
|
||||
if ([roles containsObject: SOGoRole_ObjectEraser])
|
||||
rights |= RightsDeleteAll;
|
||||
rights |= RightsDeleteAll | RightsDeleteOwn;
|
||||
if ([roles containsObject: SOGoCalendarRole_PublicModifier]
|
||||
&& [roles containsObject: SOGoCalendarRole_PrivateModifier]
|
||||
&& [roles containsObject: SOGoCalendarRole_ConfidentialModifier])
|
||||
rights |= RightsReadItems | RightsEditAll;
|
||||
rights |= RightsReadItems | RightsEditAll | RightsEditOwn | RightsFreeBusySimple | RightsFreeBusyDetailed;
|
||||
else if ([roles containsObject: SOGoCalendarRole_PublicViewer]
|
||||
&& [roles containsObject: SOGoCalendarRole_PrivateViewer]
|
||||
&& [roles containsObject: SOGoCalendarRole_ConfidentialViewer])
|
||||
@@ -135,7 +146,7 @@
|
||||
if ([roles containsObject: SOGoCalendarRole_ConfidentialDAndTViewer])
|
||||
rights |= RightsFreeBusyDetailed;
|
||||
|
||||
if (rights != 0)
|
||||
if ((rights & RightsReadItems) != 0 || (rights & RightsCreateItems) != 0 || (rights & RightsDeleteAll) != 0)
|
||||
rights |= RoleNone; /* actually "folder visible" */
|
||||
|
||||
// [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights];
|
||||
|
||||
@@ -197,7 +197,6 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
|
||||
= [MAPIStoreAppointmentWrapper wrapperWithICalEvent: masterEvent
|
||||
andUser: [userContext sogoUser]
|
||||
andSenderEmail: nil
|
||||
inTimeZone: [userContext timeZone]
|
||||
withConnectionInfo: [context connectionInfo]];
|
||||
[self addProxy: appointmentWrapper];
|
||||
}
|
||||
|
||||
@@ -96,9 +96,9 @@
|
||||
if ([roles containsObject: SOGoRole_ObjectCreator])
|
||||
rights |= RightsCreateItems;
|
||||
if ([roles containsObject: SOGoRole_ObjectEraser])
|
||||
rights |= RightsDeleteAll;
|
||||
rights |= RightsDeleteAll | RightsDeleteOwn;
|
||||
if ([roles containsObject: SOGoRole_ObjectEditor])
|
||||
rights |= RightsEditAll;
|
||||
rights |= RightsEditAll | RightsEditOwn;
|
||||
if ([roles containsObject: SOGoRole_ObjectViewer])
|
||||
rights |= RightsReadItems;
|
||||
if (rights != 0)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -102,7 +102,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
{
|
||||
value = get_SPropValue_SRow (aRow, PidTagDisplayName_string8);
|
||||
if (value)
|
||||
folderName = [NSString stringWithUTF8String: value->value.lpszA];
|
||||
folderName = [NSString stringWithUTF8String: (const char *) value->value.lpszA];
|
||||
else
|
||||
folderName = nil;
|
||||
}
|
||||
@@ -321,9 +321,9 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
if ([roles containsObject: MAPIStoreRightDeleteOwn])
|
||||
rights |= RightsDeleteOwn;
|
||||
if ([roles containsObject: MAPIStoreRightEditAll])
|
||||
rights |= RightsEditAll;
|
||||
rights |= RightsEditAll | RightsEditOwn;
|
||||
if ([roles containsObject: MAPIStoreRightDeleteAll])
|
||||
rights |= RightsDeleteAll;
|
||||
rights |= RightsDeleteAll | RightsDeleteOwn;
|
||||
if ([roles containsObject: MAPIStoreRightCreateSubfolders])
|
||||
rights |= RightsCreateSubfolders;
|
||||
if ([roles containsObject: MAPIStoreRightFolderOwner])
|
||||
|
||||
@@ -45,16 +45,28 @@
|
||||
|
||||
@implementation MAPIStoreDBMessage
|
||||
|
||||
+ (int) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
+ (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
struct SPropTagArray *properties;
|
||||
NSUInteger count;
|
||||
enum MAPITAGS faiProperties[] = { 0x68350102, 0x683c0102, 0x683e0102,
|
||||
0x683f0102, 0x68410003, 0x68420102,
|
||||
0x68450102, 0x68460003,
|
||||
// PR_VD_NAME_W, PR_VD_FLAGS, PR_VD_VERSION, PR_VIEW_CLSID
|
||||
0x7006001F, 0x70030003, 0x70070003, 0x68330048 };
|
||||
|
||||
enum MAPITAGS faiProperties[] = {
|
||||
0x68330048, /* PR_VIEW_CLSID */
|
||||
0x68350102, /* PR_VIEW_STATE */
|
||||
0x683c0102,
|
||||
0x683d0040,
|
||||
0x683e0102,
|
||||
0x683f0102, /* PR_VIEW_VIEWTYPE_KEY */
|
||||
0x68410003,
|
||||
0x68420102,
|
||||
0x68450102,
|
||||
0x68460003,
|
||||
0x7006001F, /* PR_VD_NAME_W */
|
||||
0x70030003, /* PR_VD_FLAGS */
|
||||
0x70070003 /* PR_VD_VERSION */
|
||||
};
|
||||
|
||||
size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
|
||||
|
||||
properties = talloc_zero (memCtx, struct SPropTagArray);
|
||||
@@ -76,6 +88,13 @@
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
- (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
return [[self class] getAvailableProperties: propertiesP
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
|
||||
- (id) initWithSOGoObject: (id) newSOGoObject
|
||||
inContainer: (MAPIStoreObject *) newContainer
|
||||
{
|
||||
@@ -90,18 +109,70 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
{
|
||||
id key, value;
|
||||
NSEnumerator *propEnumerator;
|
||||
NSMutableString *description;
|
||||
|
||||
description = [NSMutableString stringWithFormat: @"%@ %@. Properties: {", NSStringFromClass ([self class]),
|
||||
[self url]];
|
||||
|
||||
propEnumerator = [properties keyEnumerator];
|
||||
while ((key = [propEnumerator nextObject]))
|
||||
{
|
||||
uint32_t proptag = 0;
|
||||
if ([key isKindOfClass: [NSString class]] && [(NSString *)key intValue] > 0)
|
||||
proptag = [(NSString *)key intValue];
|
||||
else if ([key isKindOfClass: [NSNumber class]])
|
||||
proptag = [key unsignedLongValue];
|
||||
|
||||
if (proptag > 0)
|
||||
{
|
||||
const char *propTagName = get_proptag_name ([key unsignedLongValue]);
|
||||
NSString *propName;
|
||||
|
||||
if (propTagName)
|
||||
propName = [NSString stringWithCString: propTagName
|
||||
encoding: NSUTF8StringEncoding];
|
||||
else
|
||||
propName = [NSString stringWithFormat: @"0x%.4x", [key unsignedLongValue]];
|
||||
|
||||
[description appendFormat: @"'%@': ", propName];
|
||||
}
|
||||
else
|
||||
[description appendFormat: @"'%@': ", key];
|
||||
|
||||
value = [properties objectForKey: key];
|
||||
[description appendFormat: @"%@ (%@), ", value, NSStringFromClass ([value class])];
|
||||
}
|
||||
|
||||
[description appendString: @"}\n"];
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
- (uint64_t) objectVersion
|
||||
{
|
||||
NSNumber *versionNbr;
|
||||
/* Return the global counter from CN structure.
|
||||
See [MS-OXCFXICS] Section 2.2.2.1 */
|
||||
NSNumber *versionNbr, *cn;
|
||||
uint64_t objectVersion;
|
||||
|
||||
[(SOGoMAPIDBMessage *) sogoObject reloadIfNeeded];
|
||||
versionNbr = [properties objectForKey: @"version"];
|
||||
versionNbr = [properties objectForKey: @"version_number"];
|
||||
if (versionNbr)
|
||||
objectVersion = (([versionNbr unsignedLongLongValue] >> 16)
|
||||
& 0x0000ffffffffffffLL);
|
||||
objectVersion = exchange_globcnt ([versionNbr unsignedLongLongValue]);
|
||||
else
|
||||
objectVersion = ULLONG_MAX;
|
||||
{
|
||||
/* Old version which stored the CN structure not useful for searching */
|
||||
cn = [properties objectForKey: @"version"];
|
||||
if (cn)
|
||||
objectVersion = (([cn unsignedLongLongValue] >> 16)
|
||||
& 0x0000ffffffffffffLL);
|
||||
else
|
||||
objectVersion = ULLONG_MAX;
|
||||
}
|
||||
|
||||
return objectVersion;
|
||||
}
|
||||
@@ -264,13 +335,19 @@
|
||||
[properties setObject: attachmentParts forKey: @"attachments"];
|
||||
|
||||
newVersion = [[self context] getNewChangeNumber];
|
||||
newVersion = exchange_globcnt ((newVersion >> 16) & 0x0000ffffffffffffLL);
|
||||
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
||||
forKey: @"version"];
|
||||
forKey: @"version_number"];
|
||||
|
||||
/* Remove old version */
|
||||
[properties removeObjectForKey: @"version"];
|
||||
|
||||
/* Update PredecessorChangeList accordingly */
|
||||
[self _updatePredecessorChangeList];
|
||||
|
||||
[self logWithFormat: @"%d props in dict", [properties count]];
|
||||
// [self logWithFormat: @"Saving %@", [self description]];
|
||||
// [self logWithFormat: @"%d props in dict", [properties count]];
|
||||
|
||||
[sogoObject save];
|
||||
}
|
||||
|
||||
@@ -61,14 +61,15 @@ static Class MAPIStoreDBMessageK = Nil;
|
||||
|
||||
if ((uint32_t) res->ulPropTag == PidTagChangeNumber)
|
||||
{
|
||||
SEL operator;
|
||||
|
||||
value = NSObjectFromMAPISPropValue (&res->lpProp);
|
||||
cVersion = exchange_globcnt (([value unsignedLongLongValue] >> 16)
|
||||
& 0x0000ffffffffffffLL);
|
||||
version = [NSNumber numberWithUnsignedLongLong: cVersion];
|
||||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||
[self logWithFormat: @" version: %.16lx", cVersion];
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"version"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
operator = [self operatorFromRestrictionOperator: res->relop];
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"version_number"
|
||||
operatorSelector: operator
|
||||
value: version];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
|
||||
@@ -1549,7 +1549,6 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
struct Binary_r bin32;
|
||||
struct AddressBookEntryId *entryId;
|
||||
NSString *username;
|
||||
struct ldb_context *samCtx;
|
||||
|
||||
if (bin && bin->cb)
|
||||
{
|
||||
@@ -1559,8 +1558,8 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
entryId = get_AddressBookEntryId (NULL, &bin32);
|
||||
if (entryId)
|
||||
{
|
||||
samCtx = [[self context] connectionInfo]->sam_ctx;
|
||||
username = MAPIStoreSamDBUserAttribute (samCtx, @"legacyExchangeDN",
|
||||
username = MAPIStoreSamDBUserAttribute ([[self context] connectionInfo],
|
||||
@"legacyExchangeDN",
|
||||
[NSString stringWithUTF8String: entryId->X500DN],
|
||||
@"sAMAccountName");
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
/* synchronisation */
|
||||
- (BOOL) synchroniseCache;
|
||||
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl;
|
||||
|
||||
@@ -548,6 +548,115 @@ static Class NSNumberK;
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer
|
||||
{
|
||||
/* Try to synchronise old messages in versions.plist cache using an
|
||||
specific c_name. It returns a boolean indicating if the
|
||||
synchronisation was carried out succesfully.
|
||||
|
||||
It should be used as last resort, keeping synchroniseCache as the
|
||||
main sync entry point. */
|
||||
|
||||
uint64_t changeNumber;
|
||||
NSString *changeNumberStr;
|
||||
NSData *changeKey;
|
||||
NSNumber *cLastModified, *cDeleted, *cVersion;
|
||||
EOFetchSpecification *fs;
|
||||
EOQualifier *searchQualifier, *fetchQualifier;
|
||||
NSArray *fetchResults;
|
||||
NSDictionary *result;
|
||||
NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry;
|
||||
GCSFolder *ocsFolder;
|
||||
static NSArray *fields;
|
||||
|
||||
[versionsMessage reloadIfNeeded];
|
||||
currentProperties = [versionsMessage properties];
|
||||
|
||||
messages = [currentProperties objectForKey: @"Messages"];
|
||||
if (!messages)
|
||||
{
|
||||
messages = [NSMutableDictionary new];
|
||||
[currentProperties setObject: messages forKey: @"Messages"];
|
||||
[messages release];
|
||||
}
|
||||
|
||||
messageEntry = [messages objectForKey: nameInContainer];
|
||||
if (!messageEntry)
|
||||
{
|
||||
/* Fetch the message by its name */
|
||||
if (!fields)
|
||||
fields = [[NSArray alloc]
|
||||
initWithObjects: @"c_name", @"c_version", @"c_lastmodified",
|
||||
@"c_deleted", nil];
|
||||
|
||||
searchQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_name"
|
||||
operatorSelector: EOQualifierOperatorEqual
|
||||
value: nameInContainer];
|
||||
fetchQualifier = [[EOAndQualifier alloc]
|
||||
initWithQualifiers: searchQualifier,
|
||||
[self contentComponentQualifier],
|
||||
nil];
|
||||
[fetchQualifier autorelease];
|
||||
[searchQualifier release];
|
||||
|
||||
ocsFolder = [sogoObject ocsFolder];
|
||||
fs = [EOFetchSpecification
|
||||
fetchSpecificationWithEntityName: [ocsFolder folderName]
|
||||
qualifier: fetchQualifier
|
||||
sortOrderings: nil];
|
||||
fetchResults = [ocsFolder fetchFields: fields
|
||||
fetchSpecification: fs
|
||||
ignoreDeleted: NO];
|
||||
|
||||
if ([fetchResults count] == 1)
|
||||
{
|
||||
result = [fetchResults objectAtIndex: 0];
|
||||
cLastModified = [result objectForKey: @"c_lastmodified"];
|
||||
cDeleted = [result objectForKey: @"c_deleted"];
|
||||
if ([cDeleted isKindOfClass: NSNumberK] && [cDeleted intValue])
|
||||
cVersion = [NSNumber numberWithInt: -1];
|
||||
else
|
||||
cVersion = [result objectForKey: @"c_version"];
|
||||
|
||||
changeNumber = [[self context] getNewChangeNumber];
|
||||
changeNumberStr = [NSString stringWithUnsignedLongLong: changeNumber];
|
||||
|
||||
/* Create new message entry in Messages dict */
|
||||
messageEntry = [NSMutableDictionary new];
|
||||
[messages setObject: messageEntry forKey: nameInContainer];
|
||||
[messageEntry release];
|
||||
|
||||
/* Store cLastModified, cVersion and the change number */
|
||||
[messageEntry setObject: cLastModified forKey: @"c_lastmodified"];
|
||||
[messageEntry setObject: cVersion forKey: @"c_version"];
|
||||
[messageEntry setObject: changeNumberStr forKey: @"version"];
|
||||
|
||||
/* Store the change key */
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: changeNumber >> 16];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
|
||||
/* Store the changeNumber -> cLastModified mapping */
|
||||
mapping = [currentProperties objectForKey: @"VersionMapping"];
|
||||
if (!mapping)
|
||||
{
|
||||
mapping = [NSMutableDictionary new];
|
||||
[currentProperties setObject: mapping forKey: @"VersionMapping"];
|
||||
[mapping release];
|
||||
}
|
||||
[mapping setObject: cLastModified forKey: changeNumberStr];
|
||||
|
||||
/* Save the message */
|
||||
[versionsMessage save];
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
/* If message entry exists, then synchroniseCache did its job */
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl
|
||||
@@ -572,12 +681,12 @@ static Class NSNumberK;
|
||||
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber
|
||||
{
|
||||
NSDictionary *mapping;
|
||||
NSNumber *modseq;
|
||||
NSNumber *lastModified;
|
||||
|
||||
mapping = [[versionsMessage properties] objectForKey: @"VersionMapping"];
|
||||
modseq = [mapping objectForKey: changeNumber];
|
||||
lastModified = [mapping objectForKey: changeNumber];
|
||||
|
||||
return modseq;
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey
|
||||
|
||||
@@ -140,9 +140,16 @@
|
||||
[parentFolder synchroniseCache];
|
||||
changeKey = [parentFolder changeKeyForMessageWithKey: nameInContainer];
|
||||
}
|
||||
if (!changeKey)
|
||||
abort ();
|
||||
*data = [changeKey asBinaryInMemCtx: memCtx];
|
||||
if (changeKey)
|
||||
*data = [changeKey asBinaryInMemCtx: memCtx];
|
||||
else
|
||||
{
|
||||
[self warnWithFormat: @"No change key for %@ in folder %@",
|
||||
nameInContainer,
|
||||
[parentFolder url]
|
||||
];
|
||||
rc = MAPISTORE_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -180,7 +187,8 @@
|
||||
{
|
||||
uint64_t version = ULLONG_MAX;
|
||||
NSString *changeNumber;
|
||||
|
||||
BOOL synced;
|
||||
|
||||
if (!isNew)
|
||||
{
|
||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||
@@ -189,16 +197,28 @@
|
||||
{
|
||||
[self warnWithFormat: @"attempting to get change number"
|
||||
@" by synchronising folder..."];
|
||||
[(MAPIStoreGCSFolder *) container synchroniseCache];
|
||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||
changeNumberForMessageWithKey: [self nameInContainer]];
|
||||
|
||||
if (changeNumber)
|
||||
[self logWithFormat: @"got one"];
|
||||
else
|
||||
synced = [(MAPIStoreGCSFolder *) container synchroniseCache];
|
||||
if (synced)
|
||||
{
|
||||
[self errorWithFormat: @"still nothing. We crash!"];
|
||||
abort();
|
||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||
changeNumberForMessageWithKey: [self nameInContainer]];
|
||||
}
|
||||
if (!changeNumber)
|
||||
{
|
||||
[self warnWithFormat: @"attempting to get change number"
|
||||
@" by synchronising this specific message..."];
|
||||
synced = [(MAPIStoreGCSFolder *) container
|
||||
synchroniseCacheFor: [self nameInContainer]];
|
||||
if (synced)
|
||||
{
|
||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||
changeNumberForMessageWithKey: [self nameInContainer]];
|
||||
}
|
||||
if (!changeNumber)
|
||||
{
|
||||
[self errorWithFormat: @"still nothing. We crash!"];
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
version = [changeNumber unsignedLongLongValue] >> 16;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSObject+Values.h>
|
||||
|
||||
#import <EOControl/EOFetchSpecification.h>
|
||||
#import <EOControl/EOQualifier.h>
|
||||
@@ -38,7 +39,6 @@
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "MAPIStoreGCSFolder.h"
|
||||
|
||||
#import "MAPIStoreGCSMessageTable.h"
|
||||
|
||||
#undef DEBUG
|
||||
@@ -89,21 +89,31 @@
|
||||
|
||||
if (res->ulPropTag == PidTagChangeNumber)
|
||||
{
|
||||
NSString *changeNumber;
|
||||
|
||||
value = NSObjectFromMAPISPropValue (&res->lpProp);
|
||||
changeNumber = [NSString stringWithUnsignedLongLong: [(NSNumber *)value unsignedLongLongValue]];
|
||||
lastModified = [(MAPIStoreGCSFolder *)
|
||||
container lastModifiedFromMessageChangeNumber: value];
|
||||
container lastModifiedFromMessageChangeNumber: changeNumber];
|
||||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||
//[self logWithFormat: @" c_lastmodified: %@", lastModified];
|
||||
if (lastModified)
|
||||
{
|
||||
SEL operator;
|
||||
|
||||
operator = [self operatorFromRestrictionOperator: res->relop];
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_lastmodified"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
operatorSelector: operator
|
||||
value: lastModified];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
}
|
||||
else
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
{
|
||||
[self logWithFormat: @"No last modified found for: 0x%.16"PRIx64". Then no restriction applied",
|
||||
[value unsignedLongLongValue]];
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -160,23 +160,25 @@ MakeDisplayFolderName (NSString *folderName)
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
context = talloc_zero (memCtx, struct mapistore_contexts_list);
|
||||
// secondaryFolders has the names (1) Imap4Encoded and (2) asCSSIdentifier
|
||||
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
|
||||
// secondaryFolders has the names (1) Imap4Encoded ,(2) asCSSIdentifier and (3) "folder"-prefixed
|
||||
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
|
||||
currentName = [secondaryFolders objectAtIndex: count];
|
||||
// To get the real name we have to revert that (applying the decode functions)
|
||||
// in reverse order
|
||||
// To get the real name we have to revert that (applying the decode functions
|
||||
// in reverse order)
|
||||
// e.g.: Problèmes de synchronisation
|
||||
realName = [[currentName fromCSSIdentifier]
|
||||
stringByDecodingImap4FolderName];
|
||||
realName = [[[currentName substringFromIndex: 6]
|
||||
fromCSSIdentifier]
|
||||
stringByDecodingImap4FolderName];
|
||||
// And finally to represent that as URI we have to (1) asCSSIdentifier,
|
||||
// (2) Imap4Encode and (3) AddPercentEscapes
|
||||
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
|
||||
// (2) Imap4Encode (3) AddPercentEscapes and (4) add the "folder" prefix
|
||||
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
|
||||
// In the example there are no percent escapes added because is already ok
|
||||
stringData = [[[realName asCSSIdentifier]
|
||||
stringByEncodingImap4FolderName]
|
||||
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
|
||||
stringData = [NSString stringWithFormat: @"folder%@", stringData];
|
||||
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context];
|
||||
context->name = [[realName substringFromIndex: 6] asUnicodeInMemCtx: context];
|
||||
context->name = [realName asUnicodeInMemCtx: context];
|
||||
context->main_folder = false;
|
||||
context->role = MAPISTORE_MAIL_ROLE;
|
||||
context->tag = "tag";
|
||||
@@ -200,7 +202,7 @@ MakeDisplayFolderName (NSString *folderName)
|
||||
andTDBIndexing: NULL];
|
||||
accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
|
||||
folderName = [NSString stringWithFormat: @"folder%@",
|
||||
[newFolderName asCSSIdentifier]];
|
||||
[[newFolderName stringByEncodingImap4FolderName] asCSSIdentifier]];
|
||||
newFolder = [SOGoMailFolder objectWithName: folderName
|
||||
inContainer: accountFolder];
|
||||
if ([newFolder create])
|
||||
@@ -209,7 +211,7 @@ MakeDisplayFolderName (NSString *folderName)
|
||||
withString: @"%40"],
|
||||
[userName stringByReplacingOccurrencesOfString: @"@"
|
||||
withString: @"%40"],
|
||||
[[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
|
||||
[folderName stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
|
||||
else
|
||||
mapistoreURI = nil;
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
#include <util/attr.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
#include <libmapiproxy.h>
|
||||
#include <limits.h>
|
||||
#include <mapistore/mapistore.h>
|
||||
#include <mapistore/mapistore_errors.h>
|
||||
|
||||
@@ -181,7 +182,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME_UNICODE)
|
||||
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszW];
|
||||
else if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME)
|
||||
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszA];
|
||||
folderName = [NSString stringWithUTF8String: (const char *) aRow->lpProps[i].value.lpszA];
|
||||
}
|
||||
|
||||
if (folderName)
|
||||
@@ -257,6 +258,67 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
- (EOQualifier *) simplifyQualifier: (EOQualifier *) qualifier
|
||||
{
|
||||
/* Hack: Reduce the number of MODSEQ constraints to a single one as
|
||||
we assume the difference among MODSEQs will be small enough to
|
||||
return a small number of UIDs.
|
||||
|
||||
This is the only case we do simplify:
|
||||
MODSEQ >= x | MODSEQ >= y | MODSEQ >= z => MODSEQ >= min(x,y,z)
|
||||
*/
|
||||
if (qualifier && [qualifier isKindOfClass: [EOOrQualifier class]])
|
||||
{
|
||||
EOQualifier *simplifiedQualifier;
|
||||
NSArray *quals;
|
||||
NSNumber *minModseq;
|
||||
NSUInteger i, count;
|
||||
|
||||
quals = [(EOOrQualifier *)qualifier qualifiers];
|
||||
count = [quals count];
|
||||
if (count < 2)
|
||||
return qualifier;
|
||||
|
||||
minModseq = [NSNumber numberWithUnsignedLongLong: ULLONG_MAX];
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
EOQualifier *subQualifier;
|
||||
|
||||
subQualifier = [quals objectAtIndex: i];
|
||||
if ([subQualifier isKindOfClass: [EOAndQualifier class]]
|
||||
&& [[(EOAndQualifier *)subQualifier qualifiers] count] == 1)
|
||||
subQualifier = [[(EOAndQualifier *)subQualifier qualifiers] objectAtIndex: 0];
|
||||
|
||||
if ([subQualifier isKindOfClass: [EOKeyValueQualifier class]]
|
||||
&& [[(EOKeyValueQualifier *)subQualifier key] isEqualToString: @"MODSEQ"])
|
||||
{
|
||||
NSNumber *value;
|
||||
|
||||
value = (NSNumber *)[(EOKeyValueQualifier *)subQualifier value];
|
||||
if ([minModseq compare: value] == NSOrderedDescending
|
||||
&& [value unsignedLongLongValue] > 0)
|
||||
minModseq = (NSNumber *)[(EOKeyValueQualifier *)subQualifier value];
|
||||
|
||||
}
|
||||
else
|
||||
return qualifier;
|
||||
}
|
||||
|
||||
if ([minModseq unsignedLongLongValue] > 0 && [minModseq unsignedLongLongValue] < ULLONG_MAX)
|
||||
{
|
||||
simplifiedQualifier = [[EOKeyValueQualifier alloc]
|
||||
initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: minModseq];
|
||||
[simplifiedQualifier autorelease];
|
||||
return simplifiedQualifier;
|
||||
}
|
||||
}
|
||||
|
||||
return qualifier;
|
||||
}
|
||||
|
||||
- (EOQualifier *) nonDeletedQualifier
|
||||
{
|
||||
static EOQualifier *nonDeletedQualifier = nil;
|
||||
@@ -281,7 +343,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
andSortOrderings: (NSArray *) sortOrderings
|
||||
{
|
||||
NSArray *uidKeys;
|
||||
EOQualifier *fetchQualifier;
|
||||
EOQualifier *fetchQualifier, *simplifiedQualifier;
|
||||
|
||||
if ([self ensureFolderExists])
|
||||
{
|
||||
@@ -290,9 +352,10 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
|
||||
if (qualifier)
|
||||
{
|
||||
simplifiedQualifier = [self simplifyQualifier: qualifier];
|
||||
fetchQualifier
|
||||
= [[EOAndQualifier alloc] initWithQualifiers:
|
||||
[self nonDeletedQualifier], qualifier,
|
||||
[self nonDeletedQualifier], simplifiedQualifier,
|
||||
nil];
|
||||
[fetchQualifier autorelease];
|
||||
}
|
||||
@@ -594,13 +657,12 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
{
|
||||
BOOL rc = YES;
|
||||
uint64_t newChangeNum;
|
||||
NSNumber *ti, *modseq, *initialLastModseq, *lastModseq,
|
||||
*nextModseq;
|
||||
NSNumber *ti, *modseq, *lastModseq, *nextModseq;
|
||||
NSString *changeNumber, *uid, *messageKey;
|
||||
uint64_t lastModseqNbr;
|
||||
EOQualifier *searchQualifier;
|
||||
NSArray *uids, *changeNumbers;
|
||||
NSUInteger count, max;
|
||||
NSUInteger count, max, nFetched;
|
||||
NSArray *fetchResults;
|
||||
NSDictionary *result;
|
||||
NSData *changeKey;
|
||||
@@ -634,7 +696,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
}
|
||||
|
||||
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
|
||||
initialLastModseq = lastModseq;
|
||||
if (lastModseq)
|
||||
{
|
||||
lastModseqNbr = [lastModseq unsignedLongLongValue];
|
||||
@@ -680,6 +741,13 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
= [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
|
||||
context: NULL];
|
||||
|
||||
nFetched = [fetchResults count];
|
||||
if (nFetched != max) {
|
||||
[self errorWithFormat: @"Error fetching UIDs. Asked: %d Received: %d."
|
||||
@"Check the IMAP conversation for details", max, nFetched];
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
result = [fetchResults objectAtIndex: count];
|
||||
@@ -718,40 +786,37 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
}
|
||||
|
||||
/* 2. we synchronise expunged UIDs */
|
||||
if (initialLastModseq)
|
||||
fetchResults = [(SOGoMailFolder *) sogoObject
|
||||
fetchUIDsOfVanishedItems: lastModseqNbr];
|
||||
|
||||
max = [fetchResults count];
|
||||
|
||||
changeNumber = nil;
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
fetchResults = [(SOGoMailFolder *) sogoObject
|
||||
fetchUIDsOfVanishedItems: lastModseqNbr];
|
||||
|
||||
max = [fetchResults count];
|
||||
|
||||
changeNumber = nil;
|
||||
for (count = 0; count < max; count++)
|
||||
uid = [[fetchResults objectAtIndex: count] stringValue];
|
||||
if ([messages objectForKey: uid])
|
||||
{
|
||||
uid = [[fetchResults objectAtIndex: count] stringValue];
|
||||
if ([messages objectForKey: uid])
|
||||
if (!changeNumber)
|
||||
{
|
||||
if (!changeNumber)
|
||||
{
|
||||
newChangeNum = [[self context] getNewChangeNumber];
|
||||
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
|
||||
}
|
||||
[messages removeObjectForKey: uid];
|
||||
[self logWithFormat: @"Removed message entry for UID %@", uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat:@"Message entry not found for UID %@", uid];
|
||||
newChangeNum = [[self context] getNewChangeNumber];
|
||||
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
|
||||
}
|
||||
[messages removeObjectForKey: uid];
|
||||
[self logWithFormat: @"Removed message entry for UID %@", uid];
|
||||
}
|
||||
if (changeNumber)
|
||||
else
|
||||
{
|
||||
[currentProperties setObject: changeNumber
|
||||
forKey: @"SyncLastDeleteChangeNumber"];
|
||||
[mapping setObject: lastModseq forKey: changeNumber];
|
||||
foundChange = YES;
|
||||
[self logWithFormat:@"Message entry not found for UID %@", uid];
|
||||
}
|
||||
}
|
||||
if (changeNumber)
|
||||
{
|
||||
[currentProperties setObject: changeNumber
|
||||
forKey: @"SyncLastDeleteChangeNumber"];
|
||||
[mapping setObject: lastModseq forKey: changeNumber];
|
||||
foundChange = YES;
|
||||
}
|
||||
|
||||
if (foundChange)
|
||||
{
|
||||
@@ -1646,10 +1711,10 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
rights |= RightsCreateItems;
|
||||
if ([roles containsObject: SOGoRole_ObjectEraser]
|
||||
&& [roles containsObject: SOGoRole_FolderEraser])
|
||||
rights |= RightsDeleteAll;
|
||||
rights |= RightsDeleteAll | RightsDeleteOwn;
|
||||
|
||||
if ([roles containsObject: SOGoRole_ObjectEditor])
|
||||
rights |= RightsEditAll;
|
||||
rights |= RightsEditAll | RightsEditOwn;
|
||||
if ([roles containsObject: SOGoRole_ObjectViewer])
|
||||
rights |= RightsReadItems;
|
||||
if ([roles containsObject: SOGoRole_FolderCreator])
|
||||
|
||||
@@ -340,7 +340,6 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
wrapperWithICalEvent: event
|
||||
andUser: [context activeUser]
|
||||
andSenderEmail: senderEmail
|
||||
inTimeZone: [[self userContext] timeZone]
|
||||
withConnectionInfo: [context connectionInfo]];
|
||||
[appointmentWrapper retain];
|
||||
}
|
||||
@@ -522,6 +521,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSUInteger colIdx;
|
||||
NSString *stringValue;
|
||||
|
||||
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
|
||||
or less characters followed by a colon at the beginning of
|
||||
the subject, we can assume that's the subject prefix */
|
||||
subject = [self subject];
|
||||
colIdx = [subject rangeOfString: @":"].location;
|
||||
if (colIdx != NSNotFound && colIdx < 4)
|
||||
@@ -537,17 +539,45 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
- (int) getPidTagNormalizedSubject: (void **) data
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSString *subject;
|
||||
NSUInteger colIdx;
|
||||
NSString *stringValue;
|
||||
NSString *stringValue, *subject;
|
||||
NSUInteger quoteStartIdx, quoteEndIdx, colIdx;
|
||||
NSRange quoteRange;
|
||||
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
subject = [self subject];
|
||||
colIdx = [subject rangeOfString: @":"].location;
|
||||
if (colIdx != NSNotFound && colIdx < 4)
|
||||
stringValue = [[subject substringFromIndex: colIdx + 1]
|
||||
stringByTrimmingLeadSpaces];
|
||||
if (mailIsMeetingRequest)
|
||||
{
|
||||
|
||||
/* SOGo "spices up" the invitation/update mail's subject, but
|
||||
the client uses it to name the attendee's event, so we keep
|
||||
only what's inside the quotes */
|
||||
quoteStartIdx = [subject rangeOfString: @"\""].location;
|
||||
quoteEndIdx = [subject rangeOfString: @"\""
|
||||
options: NSBackwardsSearch].location;
|
||||
if (quoteStartIdx != NSNotFound
|
||||
&& quoteEndIdx != NSNotFound
|
||||
&& quoteStartIdx != quoteEndIdx)
|
||||
{
|
||||
quoteRange = NSMakeRange(quoteStartIdx + 1, quoteEndIdx - quoteStartIdx - 1);
|
||||
stringValue = [subject substringWithRange: quoteRange];
|
||||
}
|
||||
else stringValue = subject;
|
||||
}
|
||||
else
|
||||
stringValue = subject;
|
||||
{
|
||||
|
||||
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
|
||||
or less characters followed by a colon at the beginning of
|
||||
the subject, we can assume that's the subject prefix */
|
||||
colIdx = [subject rangeOfString: @":"].location;
|
||||
if (colIdx != NSNotFound && colIdx < 4)
|
||||
stringValue = [[subject substringFromIndex: colIdx + 1]
|
||||
stringByTrimmingLeadSpaces];
|
||||
else
|
||||
stringValue = subject;
|
||||
}
|
||||
if (!stringValue)
|
||||
stringValue = @"";
|
||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||
@@ -780,7 +810,6 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *contactInfos;
|
||||
NGMailAddress *ngAddress;
|
||||
NSData *entryId;
|
||||
struct ldb_context *samCtx;
|
||||
int rc;
|
||||
|
||||
if (fullMail)
|
||||
@@ -803,8 +832,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if (contactInfos)
|
||||
{
|
||||
username = [contactInfos objectForKey: @"sAMAccountName"];
|
||||
samCtx = [[self context] connectionInfo]->sam_ctx;
|
||||
entryId = MAPIStoreInternalEntryId (samCtx, username);
|
||||
entryId = MAPIStoreInternalEntryId([[self context] connectionInfo], username);
|
||||
}
|
||||
else
|
||||
entryId = MAPIStoreExternalEntryId (cn, email);
|
||||
@@ -1447,11 +1475,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSData *entryId;
|
||||
NSDictionary *contactInfos;
|
||||
SOGoUserManager *mgr;
|
||||
struct ldb_context *samCtx;
|
||||
struct mapistore_message *msgData;
|
||||
struct mapistore_message_recipient *recipient;
|
||||
|
||||
samCtx = [[self context] connectionInfo]->sam_ctx;
|
||||
[super getMessageData: &msgData inMemCtx: memCtx];
|
||||
|
||||
if (!headerSetup)
|
||||
@@ -1505,7 +1531,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
{
|
||||
username = [contactInfos objectForKey: @"sAMAccountName"];
|
||||
recipient->username = [username asUnicodeInMemCtx: msgData];
|
||||
entryId = MAPIStoreInternalEntryId (samCtx, username);
|
||||
entryId = MAPIStoreInternalEntryId ([[self context] connectionInfo], username);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1645,27 +1671,11 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
BOOL modified = NO;
|
||||
BOOL alreadyRead = NO;
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
alreadyRead = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"]
|
||||
containsObject: @"seen"];
|
||||
|
||||
/* TODO: notifications should probably be emitted from here */
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
{
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
modified = alreadyRead;
|
||||
}
|
||||
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
|
||||
else
|
||||
{
|
||||
[sogoObject addFlags: imapFlag];
|
||||
modified = !alreadyRead;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
||||
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
@@ -1708,7 +1718,10 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
BOOL modified = NO;
|
||||
BOOL seen, storedSeenFlag;
|
||||
NSNumber *value;
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
|
||||
if (value)
|
||||
@@ -1718,8 +1731,35 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
[sogoObject addFlags: @"\\Flagged"];
|
||||
else /* 0: unflagged, 1: follow up complete */
|
||||
[sogoObject removeFlags: @"\\Flagged"];
|
||||
|
||||
modified = YES;
|
||||
}
|
||||
|
||||
/* Manage seen flag on save */
|
||||
value = [properties objectForKey: @"read_flag_set"];
|
||||
if (value)
|
||||
{
|
||||
seen = [value boolValue];
|
||||
storedSeenFlag = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"] containsObject: @"seen"];
|
||||
/* We modify the flags anyway to generate a new change number */
|
||||
if (seen)
|
||||
{
|
||||
if (storedSeenFlag)
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
[sogoObject addFlags: imapFlag];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!storedSeenFlag)
|
||||
[sogoObject addFlags: imapFlag];
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
}
|
||||
modified = YES;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
||||
|
||||
if (mailIsSharingObject)
|
||||
[[self _sharingObject] saveWithMessage: self
|
||||
andSOGoObject: sogoObject];
|
||||
|
||||
@@ -181,8 +181,8 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
|
||||
else
|
||||
{
|
||||
/* Ignore other operations as IMAP only support MODSEQ >= X */
|
||||
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
|
||||
[self operatorFromRestrictionOperator: res->relop]];
|
||||
[self warnWithFormat: @"Ignoring '%@' as only supported operators are > and >=",
|
||||
NSStringFromSelector ([self operatorFromRestrictionOperator: res->relop])];
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +167,9 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
|
||||
- (BOOL) hasContentId
|
||||
{
|
||||
return ([properties
|
||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
|
||||
!= nil);
|
||||
NSString *contentId = [properties
|
||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
||||
return contentId && [contentId length] > 0;
|
||||
}
|
||||
|
||||
- (NGMimeBodyPart *) asMIMEBodyPart
|
||||
@@ -233,7 +233,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
[map addObject: contentDisposition forKey: @"content-disposition"];
|
||||
contentId = [properties
|
||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
||||
if (contentId)
|
||||
if (contentId && [contentId length] > 0)
|
||||
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
|
||||
forKey: @"content-id"];
|
||||
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
|
||||
@@ -338,13 +338,10 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
NSData *entryId;
|
||||
NSDictionary *allRecipients, *dict, *contactInfos;
|
||||
SOGoUserManager *mgr;
|
||||
struct ldb_context *samCtx;
|
||||
struct mapistore_message *msgData;
|
||||
struct mapistore_message_recipient *recipient;
|
||||
enum ulRecipClass type;
|
||||
|
||||
samCtx = [[self context] connectionInfo]->sam_ctx;
|
||||
|
||||
// [super getMessageData: &msgData inMemCtx: memCtx];
|
||||
|
||||
msgData = talloc_zero (memCtx, struct mapistore_message);
|
||||
@@ -389,7 +386,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
{
|
||||
username = [contactInfos objectForKey: @"sAMAccountName"];
|
||||
recipient->username = [username asUnicodeInMemCtx: msgData];
|
||||
entryId = MAPIStoreInternalEntryId (samCtx, username);
|
||||
entryId = MAPIStoreInternalEntryId ([[self context] connectionInfo], username);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -682,6 +679,14 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
|
||||
|
||||
if (!fromResolved)
|
||||
{
|
||||
TALLOC_CTX *local_mem_ctx;
|
||||
local_mem_ctx = talloc_new(NULL);
|
||||
if (!local_mem_ctx)
|
||||
{
|
||||
NSLog (@"%s: Out of memory", __PRETTY_FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog (@"Message without an orig from, try to guess it from PidTagSenderEntryId");
|
||||
senderEntryId = [mailProperties objectForKey: MAPIPropertyKey (PR_SENDER_ENTRYID)];
|
||||
if (senderEntryId)
|
||||
@@ -695,12 +700,12 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
|
||||
|
||||
bin32.cb = [senderEntryId length];
|
||||
bin32.lpb = (uint8_t *) [senderEntryId bytes];
|
||||
addrBookEntryId = get_AddressBookEntryId (connInfo->sam_ctx, &bin32);
|
||||
addrBookEntryId = get_AddressBookEntryId (local_mem_ctx, &bin32);
|
||||
if (addrBookEntryId && [[NSString stringWithGUID: &addrBookEntryId->ProviderUID]
|
||||
hasSuffix: @"08002b2fe182"])
|
||||
{
|
||||
/* TODO: better way to distinguish local and other ones */
|
||||
username = MAPIStoreSamDBUserAttribute (connInfo->sam_ctx, @"legacyExchangeDN",
|
||||
username = MAPIStoreSamDBUserAttribute (connInfo, @"legacyExchangeDN",
|
||||
[NSString stringWithUTF8String: addrBookEntryId->X500DN], @"sAMAccountName");
|
||||
if (username)
|
||||
{
|
||||
@@ -721,7 +726,7 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
|
||||
/* Try with One-Off EntryId */
|
||||
struct OneOffEntryId *oneOffEntryId;
|
||||
|
||||
oneOffEntryId = get_OneOffEntryId (connInfo->sam_ctx, &bin32);
|
||||
oneOffEntryId = get_OneOffEntryId (local_mem_ctx, &bin32);
|
||||
if (oneOffEntryId && [[NSString stringWithGUID: &oneOffEntryId->ProviderUID]
|
||||
hasSuffix: @"00dd010f5402"])
|
||||
{
|
||||
@@ -730,9 +735,7 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
|
||||
[fromRecipient setObject: [NSString stringWithUTF8String: oneOffEntryId->EmailAddress.lpszW]
|
||||
forKey: @"email"];
|
||||
}
|
||||
talloc_free (oneOffEntryId);
|
||||
}
|
||||
talloc_free (addrBookEntryId);
|
||||
|
||||
if ([[fromRecipient allKeys] count] > 0)
|
||||
{
|
||||
@@ -742,6 +745,8 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
|
||||
}
|
||||
|
||||
}
|
||||
/* Free entryId */
|
||||
talloc_free(local_mem_ctx);
|
||||
}
|
||||
|
||||
if (!recipients)
|
||||
@@ -1008,7 +1013,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
- (NGMimeMessage *) _generateMessageWithBcc: (BOOL) withBcc
|
||||
{
|
||||
NSString *contentType;
|
||||
NGMimeMessage *message;
|
||||
NGMimeMessage *message;
|
||||
NGMutableHashMap *headers;
|
||||
id messageBody;
|
||||
|
||||
@@ -1048,6 +1053,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
|
||||
- (int) submitWithFlags: (enum SubmitFlags) flags
|
||||
{
|
||||
enum mapistore_error rc = MAPISTORE_SUCCESS;
|
||||
NSDictionary *recipients;
|
||||
NSData *messageData;
|
||||
NSMutableArray *recipientEmails;
|
||||
@@ -1094,7 +1100,10 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
withAuthenticator: authenticator
|
||||
inContext: woContext];
|
||||
if (error)
|
||||
[self logWithFormat: @"an error occurred: '%@'", error];
|
||||
{
|
||||
[self errorWithFormat: @"an error occurred: '%@'", error];
|
||||
rc = MAPISTORE_ERR_MSG_SEND;
|
||||
}
|
||||
|
||||
// mapping = [self mapping];
|
||||
// [mapping unregisterURLWithID: [self objectId]];
|
||||
@@ -1106,7 +1115,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
[self logWithFormat: @"skipping submit of message with class '%@'",
|
||||
msgClass];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
else
|
||||
{
|
||||
connInfo = [(MAPIStoreContext *) [container context] connectionInfo];
|
||||
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, userId);
|
||||
entryId = MAPIStoreInternalEntryId (connInfo, userId);
|
||||
}
|
||||
*data = [entryId asBinaryInMemCtx: memCtx];
|
||||
|
||||
|
||||
@@ -25,12 +25,9 @@
|
||||
|
||||
#include <talloc.h>
|
||||
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalRecurrenceRule.h>
|
||||
|
||||
@class NSTimeZone;
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
|
||||
@class iCalEvent;
|
||||
@class iCalRepeatableEntityObject;
|
||||
@@ -46,7 +43,7 @@
|
||||
fromRecurrencePattern: (struct RecurrencePattern *) rp
|
||||
withExceptions: (struct ExceptionInfo *) exInfos
|
||||
andExceptionCount: (uint16_t) exInfoCount
|
||||
inTimeZone: (NSTimeZone *) tz;
|
||||
inTimeZone: (iCalTimeZone *) tz;
|
||||
|
||||
@end
|
||||
|
||||
@@ -54,7 +51,6 @@
|
||||
|
||||
- (void) fillRecurrencePattern: (struct RecurrencePattern *) rp
|
||||
withEvent: (iCalEvent *) event
|
||||
inTimeZone: (NSTimeZone *) timeZone
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
|
||||
@end
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSSet.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
@@ -35,6 +34,7 @@
|
||||
#import <NGCards/iCalRepeatableEntityObject.h>
|
||||
#import <NGCards/iCalRecurrenceRule.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalTimeZonePeriod.h>
|
||||
|
||||
#import "NSDate+MAPIStore.h"
|
||||
#import "MAPIStoreRecurrenceUtils.h"
|
||||
@@ -51,7 +51,7 @@
|
||||
fromRecurrencePattern: (struct RecurrencePattern *) rp
|
||||
withExceptions: (struct ExceptionInfo *) exInfos
|
||||
andExceptionCount: (uint16_t) exInfoCount
|
||||
inTimeZone: (NSTimeZone *) tz
|
||||
inTimeZone: (iCalTimeZone *) tz
|
||||
|
||||
{
|
||||
NSCalendarDate *startDate, *olEndDate, *untilDate, *exDate;
|
||||
@@ -63,7 +63,7 @@
|
||||
iCalWeekOccurrence weekOccurrence;
|
||||
iCalWeekOccurrences dayMaskDays;
|
||||
NSUInteger count, max;
|
||||
NSInteger bySetPos;
|
||||
NSInteger bySetPos, tzOffset;
|
||||
unsigned char maskValue;
|
||||
|
||||
[entity removeAllRecurrenceRules];
|
||||
@@ -242,9 +242,10 @@
|
||||
{
|
||||
/* The OriginalStartDate is in local time */
|
||||
exDate = [NSDate dateFromMinutesSince1601: exInfos[count].OriginalStartDate];
|
||||
tzOffset = -[[tz periodForDate: exDate] secondsOffsetFromGMT];
|
||||
exDate = [exDate dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: - [tz secondsFromGMT]];
|
||||
seconds: tzOffset];
|
||||
[exceptionDates removeObject: exDate];
|
||||
}
|
||||
}
|
||||
@@ -263,7 +264,6 @@
|
||||
|
||||
- (void) fillRecurrencePattern: (struct RecurrencePattern *) rp
|
||||
withEvent: (iCalEvent *) event
|
||||
inTimeZone: (NSTimeZone *) timeZone
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
iCalRecurrenceFrequency freq;
|
||||
@@ -279,10 +279,8 @@
|
||||
NSMutableArray *deletedDates, *modifiedDates;
|
||||
|
||||
startDate = [event firstRecurrenceStartDate];
|
||||
[startDate setTimeZone: timeZone];
|
||||
endDate = [event lastPossibleRecurrenceStartDate];
|
||||
[endDate setTimeZone: timeZone];
|
||||
|
||||
|
||||
rp->ReaderVersion = 0x3004;
|
||||
rp->WriterVersion = 0x3004;
|
||||
|
||||
|
||||
+43
-43
@@ -64,11 +64,11 @@ static BOOL initialization_done = NO;
|
||||
} \
|
||||
OC_DEBUG(5, "[SOGo] --->");
|
||||
|
||||
#define NS_CURRENT_THREAD_TRY_UNREGISTER() \
|
||||
#define NS_CURRENT_THREAD_TRY_UNREGISTER(rc) \
|
||||
if (__nsrct_thread_registered) { \
|
||||
GSUnregisterCurrentThread(); \
|
||||
} \
|
||||
OC_DEBUG(6, "[SOGo] <---");
|
||||
OC_DEBUG(6, "[SOGo] <--- [%s]", mapistore_errstr (rc));
|
||||
|
||||
#define TRYCATCH_START @try {
|
||||
#define TRYCATCH_END(pool) \
|
||||
@@ -76,7 +76,7 @@ static BOOL initialization_done = NO;
|
||||
enum mapistore_error ret = sogo_backend_handle_objc_exception(e, __PRETTY_FUNCTION__, __LINE__); \
|
||||
mapiapp_cleanup(); \
|
||||
[pool release]; \
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(); \
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(ret); \
|
||||
return ret; \
|
||||
} \
|
||||
mapiapp_cleanup();
|
||||
@@ -253,7 +253,7 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx,
|
||||
rc = MAPISTORE_ERROR;
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -291,7 +291,7 @@ sogo_backend_create_root_folder (const char *username,
|
||||
rc = MAPISTORE_ERROR;
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -322,7 +322,7 @@ sogo_backend_list_contexts(const char *username, struct indexing_context *indexi
|
||||
rc = MAPISTORE_ERROR;
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -363,7 +363,7 @@ sogo_context_get_path(void *backend_object, TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -397,7 +397,7 @@ sogo_context_get_root_folder(void *backend_object, TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -439,7 +439,7 @@ sogo_folder_open_folder(void *folder_object, TALLOC_CTX *mem_ctx, uint64_t fid,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -480,7 +480,7 @@ sogo_folder_create_folder(void *folder_object, TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -519,7 +519,7 @@ sogo_folder_delete(void *folder_object)
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -549,7 +549,7 @@ sogo_folder_get_child_count(void *folder_object, enum mapistore_table_type table
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -588,7 +588,7 @@ sogo_folder_open_message(void *folder_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -627,7 +627,7 @@ sogo_folder_create_message(void *folder_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -657,7 +657,7 @@ sogo_folder_delete_message(void *folder_object, uint64_t mid, uint8_t flags)
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -705,7 +705,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -753,7 +753,7 @@ sogo_folder_move_folder(void *folder_object, void *target_folder_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -795,7 +795,7 @@ sogo_folder_copy_folder(void *folder_object, void *target_folder_object, TALLOC_
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -831,7 +831,7 @@ sogo_folder_get_deleted_fmids(void *folder_object, TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -869,7 +869,7 @@ sogo_folder_open_table(void *folder_object, TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -903,7 +903,7 @@ sogo_folder_modify_permissions(void *folder_object, uint8_t flags,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -934,7 +934,7 @@ sogo_folder_preload_message_bodies(void *folder_object, enum mapistore_table_typ
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -967,8 +967,8 @@ sogo_message_get_message_data(void *message_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1001,7 +1001,7 @@ sogo_message_create_attachment (void *message_object, TALLOC_CTX *mem_ctx, void
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1035,7 +1035,7 @@ sogo_message_open_attachment (void *message_object, TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1069,7 +1069,7 @@ sogo_message_get_attachment_table (void *message_object, TALLOC_CTX *mem_ctx, vo
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1104,7 +1104,7 @@ sogo_message_modify_recipients (void *message_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1134,7 +1134,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1164,7 +1164,7 @@ sogo_message_save (void *message_object, TALLOC_CTX *mem_ctx)
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1194,7 +1194,7 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags)
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1234,7 +1234,7 @@ sogo_message_attachment_open_embedded_message (void *attachment_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1272,7 +1272,7 @@ sogo_message_attachment_create_embedded_message (void *attachment_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1302,7 +1302,7 @@ static enum mapistore_error sogo_table_get_available_properties(void *table_obje
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1333,7 +1333,7 @@ sogo_table_set_columns (void *table_object, uint16_t count, enum MAPITAGS *prope
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1366,7 +1366,7 @@ sogo_table_set_restrictions (void *table_object, struct mapi_SRestriction *restr
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1399,7 +1399,7 @@ sogo_table_set_sort_order (void *table_object, struct SSortOrderSet *sort_order,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1432,7 +1432,7 @@ sogo_table_get_row (void *table_object, TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1465,7 +1465,7 @@ sogo_table_get_row_count (void *table_object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1495,8 +1495,8 @@ sogo_table_handle_destructor (void *table_object, uint32_t handle_id)
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1527,7 +1527,7 @@ static enum mapistore_error sogo_properties_get_available_properties(void *objec
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1562,7 +1562,7 @@ sogo_properties_get_properties (void *object,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1592,7 +1592,7 @@ sogo_properties_set_properties (void *object, struct SRow *aRow)
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1646,7 +1646,7 @@ sogo_manager_generate_uri (TALLOC_CTX *mem_ctx,
|
||||
TRYCATCH_END(pool)
|
||||
|
||||
[pool release];
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER();
|
||||
NS_CURRENT_THREAD_TRY_UNREGISTER(MAPISTORE_SUCCESS);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ static Class MAPIStoreFolderK;
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
int rc;
|
||||
struct mapistore_connection_info *connInfo;
|
||||
uint64_t obVersion;
|
||||
|
||||
obVersion = [self objectVersion];
|
||||
@@ -204,8 +205,9 @@ static Class MAPIStoreFolderK;
|
||||
rc = MAPISTORE_ERR_NOT_FOUND;
|
||||
else
|
||||
{
|
||||
connInfo = [[self context] connectionInfo];
|
||||
*data = MAPILongLongValue (memCtx, ((obVersion << 16)
|
||||
| 0x0001));
|
||||
| connInfo->repl_id));
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
|
||||
@class NSString;
|
||||
|
||||
struct ldb_context;
|
||||
#include <mapistore/mapistore.h>
|
||||
|
||||
NSString *MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
|
||||
NSString *MAPIStoreSamDBUserAttribute (struct mapistore_connection_info *connInfo,
|
||||
NSString *userKey,
|
||||
NSString *value,
|
||||
NSString *attributeName);
|
||||
NSData *MAPIStoreInternalEntryId (struct ldb_context *, NSString *username);
|
||||
NSData *MAPIStoreInternalEntryId (struct mapistore_connection_info *connInfo, NSString *username);
|
||||
NSData *MAPIStoreExternalEntryId (NSString *cn, NSString *email);
|
||||
|
||||
#endif /* MAPISTORESAMDBUTILS_H */
|
||||
@@ -24,13 +24,18 @@
|
||||
#import <Foundation/NSString.h>
|
||||
#include <talloc.h>
|
||||
#include <ldb.h>
|
||||
#include <libmapiproxy.h>
|
||||
#include <samba/version.h>
|
||||
|
||||
#import "NSData+MAPIStore.h"
|
||||
|
||||
#import "MAPIStoreSamDBUtils.h"
|
||||
|
||||
|
||||
|
||||
|
||||
NSString *
|
||||
MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
|
||||
MAPIStoreSamDBUserAttribute (struct mapistore_connection_info *connInfo,
|
||||
NSString *userKey,
|
||||
NSString *value,
|
||||
NSString *attributeName)
|
||||
@@ -48,10 +53,20 @@ MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
|
||||
attrs[0] = [attributeName UTF8String];
|
||||
searchFormat
|
||||
= [NSString stringWithFormat: @"(&(objectClass=user)(%@=%%s))", userKey];
|
||||
ret = ldb_search (samCtx, memCtx, &res, ldb_get_default_basedn(samCtx),
|
||||
#if SAMBA_VERSION_MAJOR <= 4 && SAMBA_VERSION_MINOR < 3
|
||||
ret = ldb_search (connInfo->sam_ctx, memCtx, &res,
|
||||
ldb_get_default_basedn(connInfo->sam_ctx),
|
||||
LDB_SCOPE_SUBTREE, attrs,
|
||||
[searchFormat UTF8String],
|
||||
[value UTF8String]);
|
||||
#else
|
||||
ret = safe_ldb_search (&connInfo->sam_ctx, memCtx, &res,
|
||||
ldb_get_default_basedn(connInfo->sam_ctx),
|
||||
LDB_SCOPE_SUBTREE, attrs,
|
||||
[searchFormat UTF8String],
|
||||
[value UTF8String]);
|
||||
#endif
|
||||
|
||||
if (ret == LDB_SUCCESS && res->count == 1)
|
||||
{
|
||||
result = ldb_msg_find_attr_as_string (res->msgs[0], attrs[0], NULL);
|
||||
@@ -65,7 +80,7 @@ MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
|
||||
}
|
||||
|
||||
NSData *
|
||||
MAPIStoreInternalEntryId (struct ldb_context *samCtx, NSString *username)
|
||||
MAPIStoreInternalEntryId (struct mapistore_connection_info *connInfo, NSString *username)
|
||||
{
|
||||
static const uint8_t const providerUid[] = { 0xdc, 0xa7, 0x40, 0xc8,
|
||||
0xc0, 0x42, 0x10, 0x1a,
|
||||
@@ -82,7 +97,7 @@ MAPIStoreInternalEntryId (struct ldb_context *samCtx, NSString *username)
|
||||
type: 32
|
||||
X500DN: variable */
|
||||
|
||||
legacyDN = MAPIStoreSamDBUserAttribute (samCtx, @"sAMAccountName", username,
|
||||
legacyDN = MAPIStoreSamDBUserAttribute (connInfo, @"sAMAccountName", username,
|
||||
@"legacyExchangeDN");
|
||||
if (legacyDN)
|
||||
{
|
||||
|
||||
@@ -107,11 +107,11 @@
|
||||
if ([roles containsObject: SOGoRole_ObjectCreator])
|
||||
rights |= RightsCreateItems;
|
||||
if ([roles containsObject: SOGoRole_ObjectEraser])
|
||||
rights |= RightsDeleteAll;
|
||||
rights |= RightsDeleteAll | RightsDeleteOwn;
|
||||
if ([roles containsObject: SOGoCalendarRole_PublicModifier]
|
||||
&& [roles containsObject: SOGoCalendarRole_PrivateModifier]
|
||||
&& [roles containsObject: SOGoCalendarRole_ConfidentialModifier])
|
||||
rights |= RightsReadItems | RightsEditAll;
|
||||
rights |= RightsReadItems | RightsEditAll | RightsEditOwn;
|
||||
else if ([roles containsObject: SOGoCalendarRole_PublicViewer]
|
||||
&& [roles containsObject: SOGoCalendarRole_PrivateViewer]
|
||||
&& [roles containsObject: SOGoCalendarRole_ConfidentialViewer])
|
||||
|
||||
@@ -198,7 +198,7 @@ NSObjectFromSPropValue (const struct SPropValue *value)
|
||||
break;
|
||||
case PT_STRING8:
|
||||
result = (value->value.lpszA
|
||||
? [NSString stringWithUTF8String: value->value.lpszA]
|
||||
? [NSString stringWithUTF8String: (const char *) value->value.lpszA]
|
||||
: (id) @"");
|
||||
break;
|
||||
case PT_SYSTIME:
|
||||
|
||||
@@ -73,6 +73,8 @@ static NSMapTable *contextsTable = nil;
|
||||
[userContext autorelease];
|
||||
[contextsTable setObject: userContext forKey: username];
|
||||
}
|
||||
else
|
||||
[userContext activate];
|
||||
|
||||
return userContext;
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
mvResult = [NSMutableArray arrayWithCapacity: mvString->cValues];
|
||||
for (count = 0; count < mvString->cValues; count++)
|
||||
{
|
||||
subObject = [NSString stringWithUTF8String: mvString->lppszA[count]];
|
||||
subObject = [NSString stringWithUTF8String: (const char *) mvString->lppszA[count]];
|
||||
[mvResult addObject: subObject];
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
|
||||
- (BOOL) isNever; /* occurs on 4500-12-31 */
|
||||
|
||||
+ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date
|
||||
andRuleYear: (uint16_t) rYear;
|
||||
@end
|
||||
|
||||
NSComparisonResult NSDateCompare (id date1, id date2, void *);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSDate+MAPIStore.h"
|
||||
|
||||
#undef DEBUG
|
||||
@@ -48,7 +49,7 @@ _setupRefDate ()
|
||||
refDate = [[NSCalendarDate alloc]
|
||||
initWithYear: 1601 month: 1 day: 1
|
||||
hour: 0 minute: 0 second: 0
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"UTC"]];
|
||||
timeZone: utcTZ];
|
||||
}
|
||||
|
||||
+ (NSCalendarDate *) dateFromMinutesSince1601: (uint32_t) minutes
|
||||
@@ -128,6 +129,64 @@ _setupRefDate ()
|
||||
return [calDate yearOfCommonEra] == 4500;
|
||||
}
|
||||
|
||||
+ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date
|
||||
andRuleYear: (uint16_t) rYear
|
||||
{
|
||||
NSCalendarDate *result;
|
||||
NSInteger daysToDate;
|
||||
NSUInteger firstDayOfWeek, year;
|
||||
|
||||
/* ([MS-OXOCAL] 2.2.1.41.1) When we're provided an absolute date (i.e., it
|
||||
happens once), the SYSTEMTIME structure is enough to fill the date.
|
||||
When we're parsing a SYSTEMTIME field from a time zone rule, however, a
|
||||
relative date can be provided for the peroidicity of its periods. In this
|
||||
scenario, the wYear field is empty and we have to use the wYear field in
|
||||
the parent rule */
|
||||
if (date.wYear != 0)
|
||||
year = date.wYear;
|
||||
else
|
||||
year = rYear;
|
||||
|
||||
/* The wDay field indicates the occurrence of the wDayOfWeek within the month.
|
||||
The 5th occurrence means the last one, even if it is the 4th. */
|
||||
if (date.wDay < 5)
|
||||
{
|
||||
result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth day: 1
|
||||
hour: date.wHour minute: date.wMinute second: date.wSecond
|
||||
timeZone: utcTZ];
|
||||
[result autorelease];
|
||||
|
||||
firstDayOfWeek = [result dayOfWeek];
|
||||
|
||||
daysToDate = 7 * (date.wDay - 1) + date.wDayOfWeek - firstDayOfWeek;
|
||||
if (date.wDayOfWeek < firstDayOfWeek)
|
||||
daysToDate += 7;
|
||||
|
||||
result = [result dateByAddingYears: 0 months: 0 days: daysToDate
|
||||
hours: 0 minutes: 0
|
||||
seconds: 0];
|
||||
}
|
||||
else
|
||||
{
|
||||
result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth + 1 day: 1
|
||||
hour: date.wHour minute: date.wMinute second: date.wSecond
|
||||
timeZone: utcTZ];
|
||||
[result autorelease];
|
||||
|
||||
firstDayOfWeek = [result dayOfWeek];
|
||||
|
||||
daysToDate = date.wDayOfWeek - firstDayOfWeek;
|
||||
if (date.wDayOfWeek >= firstDayOfWeek)
|
||||
daysToDate -= 7;
|
||||
|
||||
result = [result dateByAddingYears: 0 months: 0 days: daysToDate
|
||||
hours: 0 minutes: 0
|
||||
seconds: 0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSComparisonResult
|
||||
|
||||
+11
-2
@@ -28,6 +28,8 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
@class RTFFontTable;
|
||||
|
||||
@interface RTFHandler : NSObject
|
||||
{
|
||||
NSMapTable *_charsets;
|
||||
@@ -41,6 +43,11 @@
|
||||
|
||||
- (id) initWithData: (NSData *) theData;
|
||||
- (NSMutableData *) parse;
|
||||
|
||||
- (RTFFontTable *) parseFontTable;
|
||||
- (void) mangleInternalStateWithBytesPtr: (const char*) newBytes
|
||||
andCurrentPos: (int) newCurrentPos;
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
@@ -67,6 +74,7 @@
|
||||
int font_index;
|
||||
int color_index;
|
||||
int start_pos;
|
||||
const unsigned short *charset;
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -77,12 +85,13 @@
|
||||
{
|
||||
@public
|
||||
NSString *family;
|
||||
NSString *charset;
|
||||
unsigned char charset;
|
||||
NSString *name;
|
||||
unsigned int pitch;
|
||||
unsigned int index;
|
||||
}
|
||||
|
||||
- (NSString *) description;
|
||||
@end
|
||||
|
||||
//
|
||||
@@ -96,8 +105,8 @@
|
||||
|
||||
- (void) addFontInfo: (RTFFontInfo *) theFontInfo
|
||||
atIndex: (unsigned int ) theIndex;
|
||||
|
||||
- (RTFFontInfo *) fontInfoAtIndex: (unsigned int ) theIndex;
|
||||
- (NSString *) description;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+728
-295
File diff suppressed because it is too large.
Load diff
@@ -1,8 +1,8 @@
|
||||
/* dbmsgreader.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2012 Inverse inc
|
||||
* 2015 Enrique J. Hernandez
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -33,21 +33,58 @@
|
||||
#import <SOGo/SOGoProductLoader.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
|
||||
#import <libmapi/libmapi.h>
|
||||
|
||||
#import "MAPIStoreUserContext.h"
|
||||
#import <SOGo/SOGoCacheGCSObject.h>
|
||||
|
||||
#import <SOGo/BSONCodec.h>
|
||||
#import "NSObject+PropertyList.h"
|
||||
|
||||
Class MAPIStoreUserContextK, SOGoCacheGCSObjectK;
|
||||
|
||||
Class MAPIStoreUserContextK, SOGoCacheGCSObjectK, NSStringK;
|
||||
|
||||
static void
|
||||
DumpBSONData(NSData *data)
|
||||
{
|
||||
id key, value;
|
||||
NSEnumerator *dictEnum;
|
||||
NSDictionary *dvalue;
|
||||
NSMutableString *outStr;
|
||||
NSUInteger max;
|
||||
|
||||
dvalue = [data BSONValue];
|
||||
[dvalue displayWithIndentation:0];
|
||||
printf("\n");
|
||||
max = [dvalue count];
|
||||
dictEnum = [dvalue keyEnumerator];
|
||||
NSStringK = [NSString class];
|
||||
outStr = [NSMutableString stringWithFormat: @"{ %d items\n", max];
|
||||
while ((key = [dictEnum nextObject]))
|
||||
{
|
||||
uint32_t proptag = 0;
|
||||
if ([key isKindOfClass: NSStringK] && [(NSString *)key intValue] > 0)
|
||||
proptag = [(NSString *)key intValue];
|
||||
|
||||
if (proptag > 0)
|
||||
{
|
||||
const char *propTagName = get_proptag_name (proptag);
|
||||
NSString *propName;
|
||||
|
||||
if (propTagName)
|
||||
propName = [NSString stringWithCString: propTagName
|
||||
encoding: NSUTF8StringEncoding];
|
||||
else
|
||||
propName = [NSString stringWithFormat: @"0x%.4x", [key unsignedLongValue]];
|
||||
|
||||
[outStr appendFormat: @" %@ = ", propName];
|
||||
}
|
||||
else
|
||||
[outStr appendFormat: @" %@ = ", key];
|
||||
|
||||
value = [dvalue objectForKey: key];
|
||||
[outStr appendFormat: @"(%@) %@,\n", NSStringFromClass ([value class]), value];
|
||||
}
|
||||
[outStr appendFormat: @"}\n"];
|
||||
printf ("%s\n", [outStr UTF8String]);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -67,7 +104,7 @@ DbDumpObject (NSString *username, NSString *path)
|
||||
{
|
||||
printf("record found: %p\n", record);
|
||||
content = [[record objectForKey: @"c_content"] dataByDecodingBase64];
|
||||
DumpBSONData(content);
|
||||
DumpBSONData (content);
|
||||
}
|
||||
else
|
||||
NSLog (@"record not found");
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalTimeZonePeriod.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
@@ -70,11 +71,12 @@
|
||||
#include <mapistore/mapistore_nameid.h>
|
||||
|
||||
#import "iCalEvent+MAPIStore.h"
|
||||
#import "iCalTimeZone+MAPIStore.h"
|
||||
|
||||
@implementation iCalEvent (MAPIStoreProperties)
|
||||
|
||||
- (void) _setupEventRecurrence: (NSData *) mapiRecurrenceData
|
||||
inTimeZone: (NSTimeZone *) tz
|
||||
inTimeZone: (iCalTimeZone *) tz
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
struct Binary_r *blob;
|
||||
@@ -250,10 +252,8 @@
|
||||
BOOL isAllDay;
|
||||
iCalDateTime *start, *end;
|
||||
iCalTimeZone *tz;
|
||||
NSTimeZone *userTimeZone;
|
||||
NSString *priority, *class = nil;
|
||||
NSString *priority, *class = nil, *tzDescription = nil;
|
||||
NSUInteger responseStatus = 0;
|
||||
NSInteger tzOffset;
|
||||
SOGoUser *ownerUser;
|
||||
id value;
|
||||
|
||||
@@ -274,7 +274,31 @@
|
||||
[self setAccessClass: @"PUBLIC"];
|
||||
}
|
||||
|
||||
userTimeZone = [userContext timeZone];
|
||||
/* Time zone = PidLidAppointmentTimeZoneDefinitionRecur
|
||||
or PidLidAppointmentTimeZoneDefinition[Start|End]Display */
|
||||
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionStartDisplay)];
|
||||
if (!value)
|
||||
{
|
||||
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionEndDisplay)];
|
||||
if (!value)
|
||||
{
|
||||
/* If PidLidtimeZoneStruct, TZID SHOULD come from PidLidTimeZoneDescription,
|
||||
if PidLidAppointmentTimeZoneDefinition[Start|End]Display it MUST be derived from KeyName
|
||||
(MS-OXCICAL] 2.1.3.1.1.19.1) */
|
||||
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionRecur)];
|
||||
tzDescription = [properties objectForKey: MAPIPropertyKey (PidLidTimeZoneDescription)];
|
||||
}
|
||||
}
|
||||
if (value)
|
||||
{
|
||||
tz = [[iCalTimeZone alloc] iCalTimeZoneFromDefinition: value
|
||||
withDescription: tzDescription
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
else
|
||||
/* The client is more likely to have the webmail's time zone than any other */
|
||||
tz = [iCalTimeZone timeZoneForName: [[userContext timeZone] name]];
|
||||
[(iCalCalendar *) parent addTimeZone: tz];
|
||||
|
||||
/* CREATED */
|
||||
value = [properties objectForKey: MAPIPropertyKey (PidTagCreationTime)];
|
||||
@@ -306,20 +330,13 @@
|
||||
objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)];
|
||||
if (value)
|
||||
isAllDay = [value boolValue];
|
||||
if (!isAllDay)
|
||||
{
|
||||
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
|
||||
[(iCalCalendar *) parent addTimeZone: tz];
|
||||
}
|
||||
else
|
||||
tz = nil;
|
||||
|
||||
// recurrence-id
|
||||
value
|
||||
= [properties objectForKey: MAPIPropertyKey (PidLidExceptionReplaceTime)];
|
||||
if (value)
|
||||
[self setRecurrenceId: value];
|
||||
|
||||
|
||||
// start
|
||||
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentStartWhole)];
|
||||
if (!value)
|
||||
@@ -330,15 +347,7 @@
|
||||
[start setTimeZone: tz];
|
||||
if (isAllDay)
|
||||
{
|
||||
/* when user TZ is positive (East) all-day events were not
|
||||
shown properly in SOGo UI. This day delay fixes it */
|
||||
tzOffset = [userTimeZone secondsFromGMTForDate: value];
|
||||
if (tzOffset > 0)
|
||||
{
|
||||
value = [value dateByAddingYears: 0 months: 0 days: 1
|
||||
hours: 0 minutes: 0
|
||||
seconds: 0];
|
||||
}
|
||||
/* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */
|
||||
[start setDate: value];
|
||||
[start setTimeZone: nil];
|
||||
}
|
||||
@@ -356,15 +365,7 @@
|
||||
[end setTimeZone: tz];
|
||||
if (isAllDay)
|
||||
{
|
||||
/* when user TZ is positive (East) all-day events were not
|
||||
shown properly in SOGo UI. This day delay fixes it */
|
||||
tzOffset = [userTimeZone secondsFromGMTForDate: value];
|
||||
if (tzOffset > 0)
|
||||
{
|
||||
value = [value dateByAddingYears: 0 months: 0 days: 1
|
||||
hours: 0 minutes: 0
|
||||
seconds: 0];
|
||||
}
|
||||
/* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */
|
||||
[end setDate: value];
|
||||
[end setTimeZone: nil];
|
||||
}
|
||||
@@ -467,7 +468,7 @@
|
||||
value = [properties
|
||||
objectForKey: MAPIPropertyKey (PidLidAppointmentRecur)];
|
||||
if (value)
|
||||
[self _setupEventRecurrence: value inTimeZone: userTimeZone inMemCtx: memCtx];
|
||||
[self _setupEventRecurrence: value inTimeZone: tz inMemCtx: memCtx];
|
||||
|
||||
/* alarm */
|
||||
[self _setupEventAlarmFromProperties: properties];
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
- (struct Binary_r *) asTimeZoneStructInMemCtx: (TALLOC_CTX *) memCtx;
|
||||
- (struct Binary_r *) asZoneTimeDefinitionWithFlags: (enum TZRuleFlag) flags
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value
|
||||
withDescription: (NSString *) description
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -23,11 +23,15 @@
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <NGCards/iCalByDayMask.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalTimeZonePeriod.h>
|
||||
#import <NGCards/iCalRecurrenceRule.h>
|
||||
|
||||
#import "NSString+MAPIStore.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSDate+MAPIStore.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -36,6 +40,7 @@
|
||||
#include <libmapi/libmapi.h>
|
||||
|
||||
#import "iCalTimeZone+MAPIStore.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
|
||||
@interface iCalTimeZonePeriod (MAPIStorePropertiesPrivate)
|
||||
|
||||
@@ -103,18 +108,18 @@
|
||||
{
|
||||
iCalTimeZonePeriod *period;
|
||||
struct TimeZoneStruct tz;
|
||||
int lBias, dlBias;
|
||||
int32_t lBias, dlBias;
|
||||
|
||||
memset (&tz, 0, sizeof (struct TimeZoneStruct));
|
||||
period = [self _mostRecentPeriodWithName: @"STANDARD"];
|
||||
lBias = -[period secondsOffsetFromGMT] / 60;
|
||||
tz.lBias = (uint32_t) lBias;
|
||||
tz.lBias = lBias;
|
||||
[period _fillTZDate: &tz.stStandardDate];
|
||||
period = [self _mostRecentPeriodWithName: @"DAYLIGHT"];
|
||||
if (!period)
|
||||
tz.stStandardDate.wMonth = 0;
|
||||
dlBias = -([period secondsOffsetFromGMT] / 60) - lBias;
|
||||
tz.lDaylightBias = (uint32_t) (dlBias);
|
||||
tz.lDaylightBias = dlBias;
|
||||
[period _fillTZDate: &tz.stDaylightDate];
|
||||
tz.wStandardYear = tz.stStandardDate.wYear;
|
||||
tz.wDaylightYear = tz.stDaylightDate.wYear;
|
||||
@@ -153,18 +158,220 @@
|
||||
period = [self _mostRecentPeriodWithName: @"STANDARD"];
|
||||
rule.wYear = [[period startDate] yearOfCommonEra];
|
||||
lBias = -[period secondsOffsetFromGMT] / 60;
|
||||
rule.lBias = (uint32_t) lBias;
|
||||
rule.lBias = lBias;
|
||||
[period _fillTZDate: &rule.stStandardDate];
|
||||
period = [self _mostRecentPeriodWithName: @"DAYLIGHT"];
|
||||
if (!period)
|
||||
rule.stStandardDate.wMonth = 0;
|
||||
dlBias = -([period secondsOffsetFromGMT] / 60) - lBias;
|
||||
rule.lDaylightBias = (uint32_t) (dlBias);
|
||||
rule.lDaylightBias = dlBias;
|
||||
[period _fillTZDate: &rule.stDaylightDate];
|
||||
|
||||
|
||||
return set_TimeZoneDefinition (memCtx, &definition);
|
||||
}
|
||||
|
||||
- (NSString *) _offsetStringFromOffset: (NSInteger) offset
|
||||
{
|
||||
NSInteger offsetHours, offsetMins;
|
||||
NSString *offsetSign;
|
||||
|
||||
/* The offset format is, eg, "+0200" for 2 hours 0 minutes ahead */
|
||||
if (offset < 0)
|
||||
offsetSign = @"-";
|
||||
else
|
||||
offsetSign = @"+";
|
||||
offsetHours = abs (offset) / 60;
|
||||
offsetMins = abs (offset) % 60;
|
||||
|
||||
return [NSString stringWithFormat: @"%@%d%d%d%d",
|
||||
offsetSign, offsetHours / 10, offsetHours % 10,
|
||||
offsetMins / 10, offsetMins % 10];
|
||||
|
||||
}
|
||||
|
||||
- (NSString *) _rRuleStringFromSystemTime: (struct SYSTEMTIME) date
|
||||
{
|
||||
NSString *result, *byDay;
|
||||
|
||||
/* The conversion tables between the SYSTEMTIME fields and the RRULE ones
|
||||
can be found at [MS-OXCICAL] 2.1.3.2.1 */
|
||||
if (date.wDay == 5)
|
||||
byDay = @"-1";
|
||||
else
|
||||
byDay = [NSString stringWithFormat: @"%d", date.wDay];
|
||||
|
||||
switch (date.wDayOfWeek)
|
||||
{
|
||||
case iCalWeekDaySunday:
|
||||
byDay = [byDay stringByAppendingString: @"SU"];
|
||||
break;
|
||||
case iCalWeekDayMonday:
|
||||
byDay = [byDay stringByAppendingString: @"MO"];
|
||||
break;
|
||||
case iCalWeekDayTuesday:
|
||||
byDay = [byDay stringByAppendingString: @"TU"];
|
||||
break;
|
||||
case iCalWeekDayWednesday:
|
||||
byDay = [byDay stringByAppendingString: @"WE"];
|
||||
break;
|
||||
case iCalWeekDayThursday:
|
||||
byDay = [byDay stringByAppendingString: @"TH"];
|
||||
break;
|
||||
case iCalWeekDayFriday:
|
||||
byDay = [byDay stringByAppendingString: @"FR"];
|
||||
break;
|
||||
case iCalWeekDaySaturday:
|
||||
byDay = [byDay stringByAppendingString: @"SA"];
|
||||
break;
|
||||
}
|
||||
|
||||
result = [NSString stringWithFormat: @"FREQ=YEARLY;BYDAY=%@;BYMONTH=%d", byDay, date.wMonth];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value
|
||||
withDescription: (NSString *) description
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
BOOL daylightDefined = NO, ruleFound = NO;
|
||||
iCalDateTime *daylightStart, *standardStart;
|
||||
iCalRecurrenceRule *daylightRRule, *standardRRule;
|
||||
iCalTimeZone *tz = nil;
|
||||
iCalTimeZonePeriod *daylight, *standard;
|
||||
NSCalendarDate *dlStartValue, *stStartValue;
|
||||
NSString *strOffsetFrom, *strOffsetTo, *tzID;
|
||||
char *keyName;
|
||||
struct Binary_r *binValue;
|
||||
struct SYSTEMTIME initDate;
|
||||
struct TimeZoneDefinition *definition;
|
||||
struct TZRule rule;
|
||||
uint16_t count;
|
||||
|
||||
binValue = [value asBinaryInMemCtx: memCtx];
|
||||
definition = get_TimeZoneDefinition (memCtx, binValue);
|
||||
|
||||
if (!definition)
|
||||
return nil;
|
||||
|
||||
if (!definition->cRules)
|
||||
goto end;
|
||||
|
||||
for (count = 0; count < definition->cRules; count++)
|
||||
{
|
||||
/* ([MS-OXCICAL] 2.1.3.1.1.19) The TZRule with the
|
||||
TZRULE_FLAG_EFFECTIVE_TZREG bit set in the TZRule flags field
|
||||
is the one that MUST be exported */
|
||||
if (definition->TZRules[count].flags & TZRULE_FLAG_EFFECTIVE_TZREG)
|
||||
{
|
||||
rule = definition->TZRules[count];
|
||||
ruleFound = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ruleFound)
|
||||
goto end;
|
||||
|
||||
if (!description)
|
||||
{
|
||||
/* The cbHeader field contains the size, in bytes of the Reserved (2b),
|
||||
cchKeyName (2b) keyName (variable Unicode string) and cRules (2b)
|
||||
([MS-OXOCAL] 2.2.1.41). The keyName field is a non-NULL-terminated
|
||||
char array. */
|
||||
keyName = talloc_strndup (memCtx, definition->keyName, (definition->cbHeader - 6) / 2);
|
||||
tzID = [NSString stringWithCString: keyName
|
||||
encoding: [NSString defaultCStringEncoding]];
|
||||
talloc_free (keyName);
|
||||
}
|
||||
else
|
||||
tzID = [NSString stringWithString: description];
|
||||
|
||||
tz = [iCalTimeZone groupWithTag: @"vtimezone"];
|
||||
[tz addChild: [CardElement simpleElementWithTag: @"tzid"
|
||||
value: tzID]];
|
||||
|
||||
if (rule.stStandardDate.wMonth != 0)
|
||||
daylightDefined = YES;
|
||||
|
||||
/* STANDARD TIME ([MS-OXCICAL] 2.1.3.1.1.19.2) */
|
||||
standard = [iCalTimeZonePeriod groupWithTag: @"standard"];
|
||||
|
||||
/* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */
|
||||
strOffsetFrom = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lDaylightBias)];
|
||||
[standard addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom"
|
||||
value: strOffsetFrom]];
|
||||
|
||||
/* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */
|
||||
strOffsetTo = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lStandardBias)];
|
||||
[standard addChild: [CardElement simpleElementWithTag: @"tzoffsetto"
|
||||
value: strOffsetTo]];
|
||||
|
||||
/* DTSTART & RRULE are derived from the stStandardDate and wYear properties */
|
||||
standardStart = [iCalDateTime elementWithTag: @"dtstart"];
|
||||
|
||||
initDate = rule.stStandardDate;
|
||||
stStartValue = [NSCalendarDate dateFromSystemTime: initDate
|
||||
andRuleYear: rule.wYear];
|
||||
|
||||
[standardStart setDateTime: stStartValue];
|
||||
[standard addChild: standardStart];
|
||||
|
||||
if (daylightDefined)
|
||||
{
|
||||
standardRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]];
|
||||
[standard addChild: standardRRule];
|
||||
|
||||
/* DAYLIGHT SAVING TIME ([MS-OXCICAL] 2.1.3.1.1.19.3) */
|
||||
daylight = [iCalTimeZonePeriod groupWithTag: @"daylight"];
|
||||
/* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */
|
||||
[daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom"
|
||||
value: strOffsetTo]];
|
||||
/* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */
|
||||
[daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetto"
|
||||
value: strOffsetFrom]];
|
||||
|
||||
/* DTSTART & RRULE are derived from the stDaylightDate and wYear properties */
|
||||
daylightStart = [iCalDateTime elementWithTag: @"dtstart"];
|
||||
initDate = rule.stDaylightDate;
|
||||
dlStartValue = [NSCalendarDate dateFromSystemTime: initDate
|
||||
andRuleYear: rule.wYear];
|
||||
|
||||
[daylightStart setDateTime: dlStartValue];
|
||||
[daylight addChild: daylightStart];
|
||||
|
||||
daylightRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]];
|
||||
[daylight addChild: daylightRRule];
|
||||
[tz addChild: daylight];
|
||||
}
|
||||
[tz addChild: standard];
|
||||
|
||||
end:
|
||||
|
||||
talloc_free (definition);
|
||||
return tz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust a date in this vTimeZone to its representation in UTC
|
||||
* Example: Timezone is +0001, the date is 2015-12-15 00:00:00 +0000
|
||||
* it returns 2015-12-14 23:00:00 +0000
|
||||
* @param date the date to adjust to the timezone.
|
||||
* @return a new GMT date adjusted with the offset of the timezone.
|
||||
*/
|
||||
- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date
|
||||
{
|
||||
NSCalendarDate *tmpDate;
|
||||
|
||||
tmpDate = [date copy];
|
||||
[tmpDate autorelease];
|
||||
|
||||
[tmpDate setTimeZone: utcTZ];
|
||||
|
||||
return [tmpDate addYear: 0 month: 0 day: 0
|
||||
hour: 0 minute: 0
|
||||
second: -[[self periodForDate: tmpDate] secondsOffsetFromGMT]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -500,7 +500,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60;
|
||||
ms = [NSMutableString stringWithCapacity: 256];
|
||||
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
|
||||
|
||||
[ms appendFormat: @" #adaptors=%d", [urlToAdaptor count]];
|
||||
[ms appendFormat: @" #adaptors=%d", (int)[urlToAdaptor count]];
|
||||
|
||||
[ms appendString: @">"];
|
||||
return ms;
|
||||
|
||||
@@ -414,7 +414,7 @@
|
||||
if (group)
|
||||
[str appendFormat: @"%@ (group: %@)\n", tag, group];
|
||||
else
|
||||
[str appendFormat: @"%@\n", tag, group];
|
||||
[str appendFormat: @"%@\n", tag];
|
||||
|
||||
[str appendString: [self versitString]];
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ static NGCardsSaxHandler *sax = nil;
|
||||
max = [children count];
|
||||
if (max > 0)
|
||||
{
|
||||
[str appendFormat: @"\n %d children: {\n", [children count]];
|
||||
[str appendFormat: @"\n %d children: {\n", (int)[children count]];
|
||||
for (count = 0; count < max; count++)
|
||||
[str appendFormat: @" %@\n",
|
||||
[[children objectAtIndex: count] description]];
|
||||
|
||||
@@ -32,16 +32,16 @@
|
||||
- (NSString *) iCalFormattedDateTimeString
|
||||
{
|
||||
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
|
||||
[self yearOfCommonEra], [self monthOfYear],
|
||||
[self dayOfMonth], [self hourOfDay],
|
||||
[self minuteOfHour], [self secondOfMinute]];
|
||||
(int)[self yearOfCommonEra], (int)[self monthOfYear],
|
||||
(int)[self dayOfMonth], (int)[self hourOfDay],
|
||||
(int)[self minuteOfHour], (int)[self secondOfMinute]];
|
||||
}
|
||||
|
||||
- (NSString *) iCalFormattedDateString
|
||||
{
|
||||
return [NSString stringWithFormat: @"%.4d%.2d%.2d",
|
||||
[self yearOfCommonEra], [self monthOfYear],
|
||||
[self dayOfMonth]];
|
||||
(int)[self yearOfCommonEra], (int)[self monthOfYear],
|
||||
(int)[self dayOfMonth]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -53,10 +53,10 @@
|
||||
[aString appendString: @","];
|
||||
subValue = [self objectAtIndex: count];
|
||||
|
||||
/* We MUST quote attribute values that have a ":" in them
|
||||
/* We MUST quote attribute values that have a ":" or "," in them
|
||||
and that not already quoted */
|
||||
if (asAttributes && [subValue length] > 2
|
||||
&& [subValue rangeOfString: @":"].length
|
||||
&& ([subValue rangeOfString: @":"].length || [subValue rangeOfString: @","].length)
|
||||
&& [subValue characterAtIndex: 0] != '"'
|
||||
&& ![subValue hasSuffix: @"\""])
|
||||
subValue = [NSString stringWithFormat: @"\"%@\"", subValue];
|
||||
|
||||
@@ -158,16 +158,31 @@
|
||||
NSMutableString *string;
|
||||
unsigned int len, i;
|
||||
unichar c;
|
||||
BOOL isQuoted;
|
||||
|
||||
len = [self length];
|
||||
string = [NSMutableString stringWithCapacity: len * 1.5];
|
||||
isQuoted = NO;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
c = [self characterAtIndex: i];
|
||||
|
||||
if (isQuoted)
|
||||
{
|
||||
if (c == '"')
|
||||
isQuoted = NO;
|
||||
|
||||
[string appendFormat: @"%C", c];
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
isQuoted = YES;
|
||||
[string appendFormat: @"%C", c];
|
||||
break;
|
||||
case '\\':
|
||||
[string appendString: @"\\\\"];
|
||||
break;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#import "NSCalendarDate+NGCards.h"
|
||||
|
||||
#import "iCalAlarm.h"
|
||||
#import "iCalCalendar.h"
|
||||
#import "iCalDateTime.h"
|
||||
#import "iCalEntityObject.h"
|
||||
#import "iCalEvent.h"
|
||||
@@ -268,9 +269,12 @@
|
||||
- (void) setRecurrenceId: (NSCalendarDate *) newRecId
|
||||
{
|
||||
iCalDateTime* recurrenceId;
|
||||
BOOL isMasterAllDay;
|
||||
|
||||
isMasterAllDay = [[[[self parent] events] objectAtIndex: 0] isAllDay];
|
||||
|
||||
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
|
||||
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay])
|
||||
if ([self isKindOfClass: [iCalEvent class]] && isMasterAllDay)
|
||||
[recurrenceId setDate: newRecId];
|
||||
else
|
||||
[recurrenceId setDateTime: newRecId];
|
||||
|
||||
@@ -348,7 +348,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
|
||||
if ([byDayMask occursOnDay: currentWeekDay])
|
||||
{
|
||||
if ([bySetPos containsObject:
|
||||
[NSString stringWithFormat: @"%d", currentPos]])
|
||||
[NSString stringWithFormat: @"%d", (int)currentPos]])
|
||||
monthDays[monthDay+1] = YES;
|
||||
currentPos++;
|
||||
}
|
||||
@@ -362,7 +362,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
|
||||
if ([byDayMask occursOnDay: currentWeekDay])
|
||||
{
|
||||
if ([bySetPos containsObject:
|
||||
[NSString stringWithFormat: @"%d", currentPos]])
|
||||
[NSString stringWithFormat: @"%d", (int)currentPos]])
|
||||
monthDays[monthDay] = YES;
|
||||
currentPos--;
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
- (BOOL)isEqual:(id)_other {
|
||||
if(_other == nil)
|
||||
return NO;
|
||||
if([_other class] != self->isa)
|
||||
if([_other class] != object_getClass(self))
|
||||
return NO;
|
||||
if([_other hash] != [self hash])
|
||||
return NO;
|
||||
|
||||
@@ -309,10 +309,10 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
|
||||
else if ([frequency isEqualToString:@"SECONDLY"])
|
||||
freq = iCalRecurrenceFrequenceSecondly;
|
||||
else
|
||||
freq = NSNotFound;
|
||||
freq = (iCalRecurrenceFrequency)NSNotFound;
|
||||
}
|
||||
else
|
||||
freq = NSNotFound;
|
||||
freq = (iCalRecurrenceFrequency)NSNotFound;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
@@ -1045,7 +1045,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
|
||||
if (debugOn)
|
||||
{
|
||||
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...",
|
||||
__PRETTY_FUNCTION__, _data, [_data length]);
|
||||
__PRETTY_FUNCTION__, _data, (int)[_data length]);
|
||||
}
|
||||
|
||||
if ((len = [_data length]) == 0)
|
||||
@@ -1164,7 +1164,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
|
||||
if (debugOn)
|
||||
{
|
||||
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
|
||||
__PRETTY_FUNCTION__, _source, [_source length]);
|
||||
__PRETTY_FUNCTION__, _source, (int)[_source length]);
|
||||
}
|
||||
if (!_sysId) _sysId = @"<string>";
|
||||
[self _parseString: _source];
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import getopt
|
||||
import imaplib
|
||||
import ldb
|
||||
import plistlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from samba.param import LoadParm
|
||||
|
||||
imaphost = '127.0.0.1'
|
||||
imapport = 143
|
||||
|
||||
samba_lp = LoadParm()
|
||||
sambaprivate = samba_lp.get("private dir")
|
||||
mapistorefolder = samba_lp.private_path("mapistore")
|
||||
sogoSysDefaultsFile = "/etc/sogo/sogo.conf"
|
||||
sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaults")
|
||||
|
||||
# - takes a username and optionally its password
|
||||
# - removes the entry in samba's ldap tree via ldbedit (NOTYET)
|
||||
# - remove the user's directory under mapistore/ and mapistore/SOGo
|
||||
# - cleanup Junk Folders and Sync Issues imap folders
|
||||
# - Delete the sogo_cache_folder_ table for the username.
|
||||
|
||||
def usage():
|
||||
print """
|
||||
%s [-i imaphost] ] [-p imapport] [-s sambaprivate] username [password]
|
||||
-i imaphost IMAP host to connect to [%s]
|
||||
-p imappost IMAP port to use [%d]
|
||||
-s sambaprivate samba private directory [%s]
|
||||
""" % (os.path.basename(sys.argv[0]), imaphost, imapport, sambaprivate)
|
||||
|
||||
def main():
|
||||
global sambaprivate
|
||||
global mapistorefolder
|
||||
global imaphost
|
||||
global imapport
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "i:p:s:")
|
||||
except getopt.GetoptError, err:
|
||||
print str(err)
|
||||
usage()
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-i":
|
||||
imaphost = a
|
||||
elif o == "-p":
|
||||
imapport = a
|
||||
elif o == "-s":
|
||||
sambaprivate = a
|
||||
mapistorefolder = "%s/mapistore" % (sambaprivate)
|
||||
else:
|
||||
assert False, "unhandled option"
|
||||
|
||||
argslen = len(args)
|
||||
if (argslen == 2):
|
||||
username = args[0]
|
||||
userpass = args[1]
|
||||
elif (argslen == 1):
|
||||
username = args[0]
|
||||
userpass = username
|
||||
print "Using username as password"
|
||||
else:
|
||||
usage()
|
||||
print "Specify a user (and optionally the password)"
|
||||
sys.exit(2)
|
||||
|
||||
# cleanup starts here
|
||||
try:
|
||||
imapCleanup(imaphost, imapport, username, userpass)
|
||||
except Exception as e:
|
||||
print "Error during imapCleanup, continuing: %s" % str(e)
|
||||
|
||||
try:
|
||||
mapistoreCleanup(mapistorefolder, username)
|
||||
except (shutil.Error, OSError) as e:
|
||||
print "Error during mapistoreCleanup, continuing: %s" % str(e)
|
||||
|
||||
try:
|
||||
ldbCleanup(sambaprivate, username)
|
||||
except ldb.LdbError as e:
|
||||
print "Error during ldbCleanup, continuing: %s" % str(e)
|
||||
|
||||
try:
|
||||
sqlCleanup(username)
|
||||
except Exception as e:
|
||||
print "Error during sqlCleanup, continuing: %s" % str(e)
|
||||
|
||||
def getsep(client):
|
||||
seq = None
|
||||
(code, data) = client.list("", "")
|
||||
if code == "OK" and data is not None:
|
||||
# yes this is ugly but it works on cyrus and dovecot.
|
||||
# (\\Noinferiors \\HasNoChildren) "/" INBOX
|
||||
m = re.search(".*\s+[\"\']?(.)[\"\']?\s+[\"\']?.*[\"\']?$", data[0])
|
||||
sep = m.group(1)
|
||||
return sep
|
||||
|
||||
def extractmb(si):
|
||||
inparen = False
|
||||
inquote = False
|
||||
part = []
|
||||
parts = []
|
||||
|
||||
for char in si:
|
||||
if inparen:
|
||||
if char == ")":
|
||||
inparen = False
|
||||
parts.append("".join(part))
|
||||
else:
|
||||
part.append(char)
|
||||
elif inquote:
|
||||
if char == "\"":
|
||||
inquote = False
|
||||
parts.append("".join(part))
|
||||
else:
|
||||
part.append(char)
|
||||
else:
|
||||
if char == "(":
|
||||
inparen = True
|
||||
elif char == "\"":
|
||||
inquote = True
|
||||
elif char == " ":
|
||||
part = []
|
||||
else:
|
||||
part.append(char)
|
||||
|
||||
return parts[-1]
|
||||
|
||||
def cleanupmb(mb, sep, client):
|
||||
(code, data) = client.list("%s%s" % (mb, sep), "%")
|
||||
if code == "OK":
|
||||
for si in data:
|
||||
if si is not None:
|
||||
submb = extractmb(si)
|
||||
cleanupmb(submb, sep, client)
|
||||
else:
|
||||
raise Exception, "Failure while cleaning up '%s'" % mb
|
||||
client.unsubscribe(mb)
|
||||
(code, data) = client.delete(mb)
|
||||
if code == "OK":
|
||||
print "mailbox '%s' deleted" % mb
|
||||
else:
|
||||
print "mailbox '%s' coult NOT be deleted (code = '%s')" % (mb, code)
|
||||
|
||||
def imapCleanup(imaphost, imapport, username, userpass):
|
||||
print "Starting IMAP cleanup"
|
||||
client = imaplib.IMAP4(imaphost, imapport)
|
||||
(code, data) = client.login(username, userpass)
|
||||
if code != "OK":
|
||||
raise Exception, "Login failure"
|
||||
|
||||
print "Logged in as '%s'" % username
|
||||
|
||||
sep = getsep(client)
|
||||
if not sep:
|
||||
client.logout()
|
||||
return
|
||||
|
||||
for foldername in ("Sync Issues", "Junk E-mail",
|
||||
"INBOX%sSync Issues" % sep, "INBOX%sJunk E-mail" % sep,
|
||||
"Probl&AOg-mes de synchronisation"):
|
||||
(code, data) = client.list(foldername, "%")
|
||||
if code == "OK":
|
||||
for si in data:
|
||||
if si is not None:
|
||||
mb = extractmb(si)
|
||||
cleanupmb(mb, sep, client)
|
||||
client.logout()
|
||||
|
||||
def mapistoreCleanup(mapistorefolder, username):
|
||||
print "Starting MAPIstore cleanup"
|
||||
|
||||
# delete the user's folder under the mapistore and under mapistore/SOGo
|
||||
mapistoreUserDir = "%s/%s" % (mapistorefolder, username)
|
||||
for dirpath, dirnames, filenames in os.walk(mapistoreUserDir):
|
||||
for f in filenames:
|
||||
if f != "password":
|
||||
os.unlink("%s/%s" % (dirpath,f))
|
||||
break #one level only
|
||||
|
||||
shutil.rmtree("%s/SOGo/%s" % (mapistorefolder, username), ignore_errors=True)
|
||||
|
||||
def ldbCleanup(sambaprivate, username):
|
||||
conn = ldb.Ldb("%s/openchange.ldb" % (sambaprivate))
|
||||
# find the DN of the user
|
||||
entries = conn.search(None, expression="(cn=%s)" % (username), scope=ldb.SCOPE_SUBTREE)
|
||||
if not entries:
|
||||
print "cn = %s not found in openchange.ldb" %(username)
|
||||
return
|
||||
|
||||
for entry in entries:
|
||||
# search again, but with the user's DN as a base
|
||||
subentries = conn.search(entry.dn.extended_str(), expression="(distinguishedName=*)", scope=ldb.SCOPE_SUBTREE)
|
||||
for subentry in subentries:
|
||||
print "Deleting %s" % (subentry.dn)
|
||||
conn.delete(subentry.dn)
|
||||
|
||||
def mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
|
||||
try:
|
||||
import MySQLdb
|
||||
except ImportError:
|
||||
msg ="""The python 'MySQLdb' module is not available
|
||||
On Debian based distro, install it using 'apt-get install python-mysqlbd'
|
||||
On RHEL, install it using 'yum install MySQL-python'"""
|
||||
raise Exception(msg)
|
||||
|
||||
conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
|
||||
c=conn.cursor()
|
||||
tablename="sogo_cache_folder_%s" % (username)
|
||||
c.execute("TRUNCATE TABLE %s" % tablename)
|
||||
print "Table %s emptied" % tablename
|
||||
|
||||
|
||||
def postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
|
||||
try:
|
||||
import pg
|
||||
except ImportError:
|
||||
msg ="""The python 'pg' module is not available
|
||||
On Debian based distro, install it using 'apt-get install python-pygresql'
|
||||
On RHEL, install it using 'yum install python-pgsql'"""
|
||||
raise Exception(msg)
|
||||
|
||||
conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname)
|
||||
tablename = "sogo_cache_folder_%s" % username
|
||||
conn.query("DELETE FROM %s" % tablename)
|
||||
print "Table '%s' emptied" % tablename
|
||||
|
||||
def getOCSFolderInfoURL():
|
||||
global sogoSysDefaultsFile, sogoUserDefaultsFile
|
||||
|
||||
OCSFolderInfoURL = ""
|
||||
|
||||
# read defaults from defaults files
|
||||
# order is important, user defaults must have precedence
|
||||
for f in [sogoSysDefaultsFile, sogoUserDefaultsFile]:
|
||||
if os.path.exists(f):
|
||||
# FIXME: this is ugly, we should have a python plist parser
|
||||
# plistlib only supports XML plists.
|
||||
# the following magic replaces this shell pipeline:
|
||||
# sogo-tool dump-defaults -f %s | awk -F\\" '/ OCSFolderInfoURL =/ {print $2}'
|
||||
p1 = subprocess.Popen(["sogo-tool", "dump-defaults", "-f", f], stdout=subprocess.PIPE)
|
||||
p2 = subprocess.Popen(["awk", "-F\"", "/ OCSFolderInfoURL =/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
|
||||
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
|
||||
tmp = p2.communicate()[0]
|
||||
|
||||
if tmp: OCSFolderInfoURL = tmp
|
||||
|
||||
return OCSFolderInfoURL
|
||||
|
||||
def asCSSIdentifier(inputString):
|
||||
cssEscapingCharMap = {"_" : "_U_",
|
||||
"." : "_D_",
|
||||
"#" : "_H_",
|
||||
"@" : "_A_",
|
||||
"*" : "_S_",
|
||||
":" : "_C_",
|
||||
"," : "_CO_",
|
||||
" " : "_SP_",
|
||||
"'" : "_SQ_",
|
||||
"&" : "_AM_",
|
||||
"+" : "_P_"}
|
||||
|
||||
newChars = []
|
||||
|
||||
for c in inputString:
|
||||
if c in cssEscapingCharMap:
|
||||
newChars.append(cssEscapingCharMap[c])
|
||||
else:
|
||||
newChars.append(c)
|
||||
|
||||
return "".join(newChars)
|
||||
|
||||
def sqlCleanup(username):
|
||||
print "Starting SQL cleanup"
|
||||
OCSFolderInfoURL = getOCSFolderInfoURL()
|
||||
if OCSFolderInfoURL is None:
|
||||
raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username))
|
||||
|
||||
# postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info
|
||||
m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL)
|
||||
|
||||
if not m:
|
||||
raise Exception("Unable to parse OCSFolderInfoURL: %s" % OCSFolderInfoURL)
|
||||
|
||||
proto = m.group(1)
|
||||
dbuser = m.group(2)
|
||||
dbpass = m.group(3)
|
||||
dbhost = m.group(4)
|
||||
dbport = m.group(5)
|
||||
dbname = m.group(6)
|
||||
# 7 is folderinfo table
|
||||
|
||||
encodedUserName = asCSSIdentifier(username)
|
||||
|
||||
if (proto == "postgresql"):
|
||||
postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
|
||||
elif (proto == "mysql"):
|
||||
mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
|
||||
else:
|
||||
raise Exception("Unknown sql proto: %s" % (proto))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
@@ -6,20 +6,52 @@ import re
|
||||
|
||||
DEBUG=False
|
||||
|
||||
dir_mappings = {"../UI/Templates":"../UI/Common",
|
||||
"../UI/Templates/AdministrationUI":"../UI/AdministrationUI",
|
||||
"../UI/Templates/Appointments":"../SoObjects/Appointments",
|
||||
"../UI/Templates/ContactsUI":"../UI/Contacts",
|
||||
"../UI/Templates/MailerUI":"../UI/MailerUI",
|
||||
"../UI/Templates/MailPartViewers":"../UI/MailPartViewers",
|
||||
"../UI/Templates/MainUI":"../UI/MainUI",
|
||||
"../UI/Templates/PreferencesUI":"../UI/PreferencesUI",
|
||||
"../UI/Templates/SchedulerUI":"../UI/Scheduler"
|
||||
}
|
||||
dir_mappings = {
|
||||
# .wox
|
||||
"../UI/Templates":"../UI/Common",
|
||||
"../UI/Templates/AdministrationUI":"../UI/AdministrationUI",
|
||||
"../UI/Templates/Appointments":"../SoObjects/Appointments",
|
||||
"../UI/Templates/ContactsUI":"../UI/Contacts",
|
||||
"../UI/Templates/MailerUI":"../UI/MailerUI",
|
||||
"../UI/Templates/MailPartViewers":"../UI/MailPartViewers",
|
||||
"../UI/Templates/MainUI":"../UI/MainUI",
|
||||
"../UI/Templates/PreferencesUI":"../UI/PreferencesUI",
|
||||
"../UI/Templates/SchedulerUI":"../UI/Scheduler",
|
||||
# .toolbars
|
||||
"../UI/AdministrationUI/Toolbars":"../UI/AdministrationUI",
|
||||
"../UI/Contacts/Toolbars":"../UI/Contacts",
|
||||
"../UI/MailerUI/Toolbars":"../UI/MailerUI",
|
||||
"../UI/PreferencesUI/Toolbars":"../UI/PreferencesUI",
|
||||
"../UI/Scheduler/Toolbars":"../UI/Scheduler",
|
||||
# .js
|
||||
"../UI/WebServerResources/generic.js":"../UI/Common",
|
||||
"../UI/WebServerResources/UIxAclEditor.js":"../UI/Common",
|
||||
"../UI/WebServerResources/SOGoRootPage.js":"../UI/MainUI",
|
||||
"../UI/WebServerResources/SOGoRootPage.js":"../UI/MainUI",
|
||||
"../UI/WebServerResources/PasswordPolicy.js":"../UI/AdministrationUI",
|
||||
"../UI/WebServerResources/ContactsUI.js":"../UI/Contacts",
|
||||
"../UI/WebServerResources/UIxContactEditor.js":"../UI/Contacts",
|
||||
"../UI/WebServerResources/UIxContactsUserFolders.js":"../UI/Contacts",
|
||||
"../UI/WebServerResources/UIxContactsUserRightsEditor.js":"../UI/Contacts",
|
||||
"../UI/WebServerResources/MailerUI.js":"../UI/MailerUI",
|
||||
"../UI/WebServerResources/MailerUIdTree.js":"../UI/MailerUI",
|
||||
"../UI/WebServerResources/UIxMailEditor.js":"../UI/MailerUI",
|
||||
"../UI/WebServerResources/UIxMailSearch.js":"../UI/MailerUI",
|
||||
"../UI/WebServerResources/UIxMailUserRightsEditor.js":"../UI/MailerUI",
|
||||
"../UI/WebServerResources/UIxPreferences.js":"../UI/PreferencesUI",
|
||||
"../UI/WebServerResources/UIxFilterEditor.js":"../UI/PreferencesUI",
|
||||
"../UI/WebServerResources/SchedulerUI.js":"../UI/Scheduler",
|
||||
"../UI/WebServerResources/UIxCalUserRightsEditor.js":"../UI/Scheduler",
|
||||
"../UI/WebServerResources/UIxCalViewPrint.js":"../UI/Scheduler",
|
||||
}
|
||||
|
||||
def get_translations(path):
|
||||
def get_translations(path, filename):
|
||||
try:
|
||||
transpath = dir_mappings.get(path, path)
|
||||
transpath = dir_mappings.get(path)
|
||||
if not transpath:
|
||||
transpath = dir_mappings.get(path + '/' + filename)
|
||||
if not transpath:
|
||||
transpath = path
|
||||
transname = transpath + '/English.lproj/Localizable.strings'
|
||||
transall = open(transname).read()
|
||||
except:
|
||||
@@ -29,6 +61,8 @@ def get_translations(path):
|
||||
|
||||
def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=()):
|
||||
for path, dirs, files in os.walk(rootdir):
|
||||
if (os.path.basename(path) in greylist):
|
||||
continue
|
||||
filelist = [f for f in files if f[(-1 * len(extention)):] == extention]
|
||||
if filelist:
|
||||
for filename in filelist:
|
||||
@@ -40,7 +74,7 @@ def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=(
|
||||
values = [r.groups()[0] for r in [recomp.search(l) for l in lines] if r]
|
||||
if values:
|
||||
#- Get the current english translations for the path
|
||||
transgood = get_translations(path)
|
||||
transgood = get_translations(path, filename)
|
||||
if not transgood:
|
||||
print "No translation file found for %s, skipping %s" % (path, pathname)
|
||||
continue
|
||||
@@ -53,7 +87,8 @@ def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=(
|
||||
if found:
|
||||
if DEBUG: print "\t", '[%s] FOUND -- "%s"' % found.groups()
|
||||
else:
|
||||
notfound.append("-->\t[%s] ==== Not Found ====" % value)
|
||||
#notfound.append("-->\t[%s] ==== Not Found ====" % value)
|
||||
notfound.append("""-->\t"%s" = "%s";""" % (value, value))
|
||||
if notfound:
|
||||
if not DEBUG:print pathname
|
||||
print "\n".join(notfound)
|
||||
@@ -69,14 +104,20 @@ def main():
|
||||
print 'Usage:', sys.argv[0], '[-g]\n\t\t-g: debug will show matching also'
|
||||
sys.exit(1)
|
||||
|
||||
greylist = ('UIxFilterEditor.wox')
|
||||
|
||||
#- Get only the label:value from all lines
|
||||
recomp = re.compile(' label:[^=]*="(.*?)"')
|
||||
find_missing_translations('../UI', 'wox', recomp, greylist)
|
||||
recomp = re.compile('.label:[^=]*="([^$].*?)"')
|
||||
find_missing_translations('../UI', 'wox', recomp, ())
|
||||
|
||||
#- [self labelForKey: @"Issuer"]
|
||||
recomp = re.compile('\[self labelForKey: @"(.*?)"\]')
|
||||
find_missing_translations('../UI', 'm', recomp, ())
|
||||
|
||||
#- tooltip = "Switch to day view"
|
||||
recomp = re.compile(' tooltip = "(.*?)";')
|
||||
find_missing_translations('../UI', 'toolbar', recomp, ('Resources'))
|
||||
|
||||
#- _("Reminder")
|
||||
recomp = re.compile(' _\("(.*?)"\)')
|
||||
find_missing_translations('../UI/WebServerResources', 'js', recomp, ())
|
||||
|
||||
main()
|
||||
@@ -6,7 +6,6 @@ vevent_class2 = "(حدث سري)";
|
||||
vtodo_class0 = "(مهمة عامة)";
|
||||
vtodo_class1 = "(مهمة خاصة)";
|
||||
vtodo_class2 = "(مهمة سرية)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "أُنشِئ الحدث \"%{Summary}\"";
|
||||
"The event \"%{Summary}\" was deleted" = "حُذِفَ الحدث \"%{Summary}\"";
|
||||
@@ -14,7 +13,6 @@ vtodo_class2 = "(مهمة سرية)";
|
||||
"The following attendees(s) were notified" = "تم ابلاغ المدعو (المدعويين) الأتي أسماءهم";
|
||||
"The following attendees(s) were added" = "تم أضافة المدعو (المدعويين) الأتي أسماءهم";
|
||||
"The following attendees(s) were removed" = "تم مسح المدعو (المدعويين) الأتي أسماءهم";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "التقويم";
|
||||
"startDate_label" = "البداية";
|
||||
@@ -23,20 +21,17 @@ vtodo_class2 = "(مهمة سرية)";
|
||||
"location_label" = "المكان";
|
||||
"summary_label" = "الملخص:";
|
||||
"comment_label" = "التعليق:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "دعوة الحدث: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(أرسلت بواسطة٪ {SentBy})";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "وقد دعت%{Organizer} %{SentByText} الى %{Summary} . ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate}⏎\nالوصف:%{Description}\n";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}قام بدعوتك %{Summary}. ⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}\n";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "الحدث الغي : \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "ألغى%{Organizer} %{SentByText} هذا الحدث:.%{Summary} ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate} ⏎\nالوصف:%{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}قام بإلغاء هذا الحدث: %{Summary}.⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "موعد \"%{Summary}\" بتاريخ %{OldStartDate} قد تغير";
|
||||
@@ -46,7 +41,6 @@ vtodo_class2 = "(مهمة سرية)";
|
||||
= " لقد تغيرت المحددات التالية في الاجتماع \"%{Summary}\":";
|
||||
"Please accept or decline those changes."
|
||||
= "يرجى قبول أو رفض تلك التغييرات.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "دعوةٌ مقبولة: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "دعوةٌ مرفوضة: \"%{Summary}\"";
|
||||
@@ -60,7 +54,6 @@ vtodo_class2 = "(مهمة سرية)";
|
||||
= "وقد فوض%{Attendee} %{SentByText} الدعوة إلى %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText} لم يقرر حتى الآن حضورهذا الحدث الخاص بك.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "لا يمكن الوصول إلى الموارد: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "الحد الأقصى لعدد الحجوزات في وقت واحد (%{NumberOfSimultaneousBookings}) وصلت للموارد \"%{Cn} %{SystemEmail}\" . الحدث المتضارب في الموعد هو \"٪ {EventTitle}\"، ويبدأ فى٪ {StartDate}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Isilpeko ekitaldia)";
|
||||
vtodo_class0 = "(Zeregin publikoa)";
|
||||
vtodo_class1 = "(Zeregin pribatua)";
|
||||
vtodo_class2 = "(Isilpeko egitekoa)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "\"%{Summary}\" ekitaldia sortu da";
|
||||
"The event \"%{Summary}\" was deleted" = "\"%{Summary}\" ekitaldia ezabatu da";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
|
||||
"The following attendees(s) were notified" = "Ondorengo partaidea(k) jakinarazi d(ir)a";
|
||||
"The following attendees(s) were added" = "Ondorengo partaidea(k) gehitu d(ir)a";
|
||||
"The following attendees(s) were removed" = "Ondorengo partaidea(k) ezabatu d(ir)a";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Egutegia";
|
||||
"startDate_label" = "Hasi";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Isilpeko egitekoa)";
|
||||
"location_label" = "Kokapena";
|
||||
"summary_label" = "Laburpena:";
|
||||
"comment_label" = "Iruzkina:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Ekitaldirako gonbidapena: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(%{SentBy}-k bidalia) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}.-ra\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDeskribapena: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}-ra \n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nEnd: %{EndDate}-an, %{EndTime}-tan\nDeskribapena: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Ekitaldia bertan behera utzi da: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} ekitaldi hau bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDEskribapena: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}-k honako ekitaldia bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nAmaiera: %{EndDate}-an %{EndTime}-tan\nDeskribapena: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "%{OldStartDate} eguneko \"%{Summary}\" hitzordua aldatu da";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
|
||||
= " \"%{Summary}\" bilerako ondorengo parametroak aldatu dira:";
|
||||
"Please accept or decline those changes."
|
||||
= "Mesedez, onartu edo ezetsi ondorengo aldaketak.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Gonbidapena onartua: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Ezetzitako gonbidapena: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
|
||||
= "%{Attendee} %{SentByText}-k zure gonbidapena %{Delegate}-ri delegatu dio.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}-k ez du oraindik zure gonbidapenari buruz erabakirik hartu.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ezin da honako baliabidea atzitu: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" baliabidearentzako gehienezko erreserba kopurura (%{NumberOfSimultaneousBookings}) iritsi gara. Gatazka sortu duen ekitaldia \"%{EventTitle}\" da, eta %{StartDate}-n hasten da.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Evento Confidencial)";
|
||||
vtodo_class0 = "(Tarefa Pública)";
|
||||
vtodo_class1 = "(Tarefa Privada)";
|
||||
vtodo_class2 = "(Tarefa Confidencial)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "O evento \"%{Summary}\" foi criado";
|
||||
"The event \"%{Summary}\" was deleted" = "O evento \"%{Summary}\" foi removido";
|
||||
@@ -15,29 +14,25 @@ vtodo_class2 = "(Tarefa Confidencial)";
|
||||
"The following attendees(s) were notified" = "Estes participantes foram notificados";
|
||||
"The following attendees(s) were added" = "Estes participantes foram adicionados";
|
||||
"The following attendees(s) were removed" = "Estes participantes foram removidos";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Calendário";
|
||||
"startDate_label" = "Início";
|
||||
"endDate_label" = "Fim";
|
||||
"due_label" = "Data de Vencimento:";
|
||||
"location_label" = "Local";
|
||||
"location_label" = "Localização";
|
||||
"summary_label" = "Resumo:";
|
||||
"comment_label" = "Comentário:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Convite do Evento: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(enviado por %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}convidou você para %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} convidou você para %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Evento Cancelado: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}cancelou este evento: %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} cancelou este evento: %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "O compromisso \"%{Summary}\" de %{OldStartDate} mudou";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
|
||||
= "Os seguintes parâmetros mudaram na reunião \"%{Summary}\" :\n\n";
|
||||
"Please accept or decline those changes."
|
||||
= "Por favor, aceitar ou recusar as alterações.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Convite aceito: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Convite recusado: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
|
||||
= "%{Attendee} %{SentByText} delegou o convite para %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}foi ainda não decidiu seu convite ao evento.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Não foi possível acessar o recurso: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "O número máximo de reservas simultaneas (%{NumberOfSimultaneousBookings}) acabou para o recurso \"%{Cn} %{SystemEmail}\". O evento conflitante é \"%{EventTitle}\", e inicia em %{StartDate}.";
|
||||
@@ -7,37 +7,32 @@ vevent_class2 = "(Esdeveniment confidencial)";
|
||||
vtodo_class0 = "(Tasca pública)";
|
||||
vtodo_class1 = "(Tasca privada)";
|
||||
vtodo_class2 = "(Tasca confidencial)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Es va crear l'esdeveniment \"%{Summary}\" ";
|
||||
"The event \"%{Summary}\" was deleted" = "Es va esborrar l'esdeveniment \"%{Summary}\" ";
|
||||
"The event \"%{Summary}\" was updated" = "Es va modificar l'esdeveniment \"%{Summary}\" ";
|
||||
"The following attendees(s) were notified" = "I va notificar als següents participants ";
|
||||
"The following attendees(s) were added" = "Es van agregar els següents participants ";
|
||||
"The following attendees(s) were added" = "S'han afegit els següents participants";
|
||||
"The following attendees(s) were removed" = "Heu suprimit els assistents següents";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Calendari";
|
||||
"startDate_label" = "Inici";
|
||||
"endDate_label" = "Final";
|
||||
"due_label" = "Data límit:";
|
||||
"due_label" = "Data de venciment";
|
||||
"location_label" = "Lloc";
|
||||
"summary_label" = "Resum:";
|
||||
"comment_label" = "Comentari:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Invitació a l'esdeveniment: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(enviada per %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Esdeveniment suspès: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "La cita \"%{Summary}\" per al dia %{OldStartDate} ha canviat";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tasca confidencial)";
|
||||
= "Els paràmetres següents han canviat en la reunió \"%{Summary}\":";
|
||||
"Please accept or decline those changes."
|
||||
= "Si us plau, accepteu o rebutgeu aquests canvis.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Invitació acceptada: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Invitació rebutjada; \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tasca confidencial)";
|
||||
= "%{Attendee} %{SentByText}ha delegat la invitació en %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}encara no s'ha decidit sobre la invitació a l'esdeveniment.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "No es pot accedir al recurs: \"%{Cn} %{SystemEmail}\" ";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Nombre màxim de reserves simultànies (%{NumberOfSimultaneousBookings}) assolit per al recurs \"%{Cn} %{SystemEmail}\". L'esdeveniment que entra en conflicte és \"%{EventTitle}\", i comença el %{StartDate}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(機密事件)";
|
||||
vtodo_class0 = "(公開任務)";
|
||||
vtodo_class1 = "(私人任務)";
|
||||
vtodo_class2 = "(機密任務)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "事件 \"%{Summary}\" 已建立";
|
||||
"The event \"%{Summary}\" was deleted" = "事件\"%{Summary}\" 已刪除";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(機密任務)";
|
||||
"The following attendees(s) were notified" = "已通知下列出席者";
|
||||
"The following attendees(s) were added" = "已增加下列出席者";
|
||||
"The following attendees(s) were removed" = "已移除下列出席者";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "行事曆";
|
||||
"startDate_label" = "開始";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(機密任務)";
|
||||
"location_label" = "地點";
|
||||
"summary_label" = "事件主題:";
|
||||
"comment_label" = "備註:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "邀請事件: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(來自 %{SentBy})";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} 邀請您參加 %{Summary}。\n\n起始日期: %{StartDate}\n結束日期: %{EndDate}\n備註說明: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} 邀請您參加 %{Summary}。\n\n起始時間: %{StartDate} %{StartTime}\n結束時間: %{EndDate} %{EndTime}\n備註說明: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "取消事件 : \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} 己取消事件: %{Summary}。\n\n起始日期: %{StartDate}\n結束日期: %{EndDate}\n備註說明: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} 己取消了事件: %{Summary}。\n\n起始時間: %{StartDate} %{StartTime}\n結束時間: %{EndDate} %{EndTime}\n備註說明: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= " 安排於%{OldStartDate} 的事件 \"%{Summary}\" 已變更";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(機密任務)";
|
||||
= "已變更會議 \"%{Summary}\" 下列參數:";
|
||||
"Please accept or decline those changes."
|
||||
= "請接受或拒絶這些變更。";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "接受邀請: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "拒絶邀請: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(機密任務)";
|
||||
= "\"%{Attendee} %{SentByText}已將您的邀請委任給%{Delegate}。";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}還沒有決定是否接受您的邀請。";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "無法讀取資源: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" 的衝突事件己達上限 ( %{NumberOfSimultaneousBookings} )。衝突事件 \"%{EventTitle}\" 的起始日期為 %{StartDate}。";
|
||||
@@ -0,0 +1,60 @@
|
||||
"Inviting the following persons is prohibited:" = "Pozivanje slijedeće osobe je zabranjeno:";
|
||||
"Personal Calendar" = "Osobni kalendar";
|
||||
vevent_class0 = "(Javni događaj)";
|
||||
vevent_class1 = "(Osobni događaj)";
|
||||
vevent_class2 = "(Povjerljivi događaj)";
|
||||
|
||||
vtodo_class0 = "(Javni zadatak)";
|
||||
vtodo_class1 = "(Osobni zadatak)";
|
||||
vtodo_class2 = "(Povjerljivi zadatak)";
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Događaj \"% {Summary}\" je stvoren";
|
||||
"The event \"%{Summary}\" was deleted" = "Događaj \"% {Summary}\" je izbrisan";
|
||||
"The event \"%{Summary}\" was updated" = "Događaj \"% {Summary}\" je ažuriran";
|
||||
"The following attendees(s) were notified" = "Sljedeći sudionici su obaviješteni";
|
||||
"The following attendees(s) were added" = "Sljedeći sudionici su dodani";
|
||||
"The following attendees(s) were removed" = "Sljedeći sudionici su uklonjeni";
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalendar";
|
||||
"startDate_label" = "Start";
|
||||
"endDate_label" = "Kraj";
|
||||
"due_label" = "Datum dospijeća";
|
||||
"location_label" = "Mjesto";
|
||||
"summary_label" = "Sažetak";
|
||||
"comment_label" = "Komentar";
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Pozivnica: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(poslao %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vas je pozvao na %{Summary}.\n\nPočetak: %{StartDate}\nKraj: %{EndDate}\nOpis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vas je pozvao na %{Summary}.\n\nPočetak: %{StartDate} at %{StartTime}\nKraj: %{EndDate} at %{EndTime}\nOpis: %{Description}";
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Događaj otkazan: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}je otkazao ovaj događaj: %{Summary}.\n\nPočetak: %{StartDate}\nKraj: %{EndDate}\nOpis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}je otkazao ovaj događaj: %{Summary}.\nPočetak: %{StartDate} at %{StartTime}\nKraj: %{EndDate} at %{EndTime}\nOpis: %{Description}";
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Sastanak \"%{Summary}\" za %{OldStartDate} je promjenjen";
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
|
||||
= "Sastanak \"%{Summary}\" iz %{OldStartDate} u %{OldStartTime} je promjenjen";
|
||||
"The following parameters have changed in the \"%{Summary}\" meeting:"
|
||||
= "Sljedeći parametri su se promijenili u \"%{Summary}\" sastanak:";
|
||||
"Please accept or decline those changes."
|
||||
= "Molim prihvatite ili odbacite promjene.";
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Prihvaćen poziv:: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Odbijen poziv:: \"%{Summary}\"";
|
||||
"Delegated invitation: \"%{Summary}\"" = "Proslijediti poziv: \"%{Summary}\"";
|
||||
"Not yet decided on invitation: \"%{Summary}\"" = "Još nije odlučeno o pozivu: \"%{Summary}\"";
|
||||
"%{Attendee} %{SentByText}has accepted your event invitation."
|
||||
= "%{Attendee} %{SentByText}je prihvatio vaš poziv događaj.";
|
||||
"%{Attendee} %{SentByText}has declined your event invitation."
|
||||
= "%{Attendee} %{SentByText}je odbio pozivnicu za događaj.";
|
||||
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
|
||||
= "%{Attendee} %{SentByText}je proslijedio poziv za %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}još nije odlučio o vašem pozivu za događaj.";
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ne mogu pristupiti: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimalni broj istovremenih rezervacija (%{NumberOfSimultaneousBookings}) dosegnut je za \"%{Cn} %{SystemEmail}\". Problematični događaj je \"%{EventTitle}\" i počinje %{StartDate}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Skrytá událost)";
|
||||
vtodo_class0 = "(Veřejný úkol)";
|
||||
vtodo_class1 = "(Soukromý úkol)";
|
||||
vtodo_class2 = "(Skrytý úkol)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Událost \"%{Summary}\" byla vytvořena";
|
||||
"The event \"%{Summary}\" was deleted" = "Událost \"%{Summary}\" byla smazána";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
"The following attendees(s) were notified" = "Následující účastníci byli upozorněni";
|
||||
"The following attendees(s) were added" = "Následující účastníci byli přidáni";
|
||||
"The following attendees(s) were removed" = "Následující účastníci byli odebráni";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalendář";
|
||||
"startDate_label" = "Začátek";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
"location_label" = "Místo";
|
||||
"summary_label" = "Název:";
|
||||
"comment_label" = "Komentář:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Pozvání na událost: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(poslal/a %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Zrušení události: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Schůzka \"%{Summary}\" na %{OldStartDate} byla změněna";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
= "Následující parametry byly ve schůzce \"%{Summary}\" změněny:";
|
||||
"Please accept or decline those changes."
|
||||
= "Prosím přijměte nebo odmítněte tyto změny.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Přijaté pozvání: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Odmítnuté pozvání: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
= "%{Attendee} %{SentByText}delegoval/a Vaše pozvání na %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}dosud o Vašem pozvání k události nerozhodl/a.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Nedostupný zdroj: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Byl dosažen maximální počet současných rezervací\n(%{NumberOfSimultaneousBookings}) pro zdroj \"%{Cn} %{SystemEmail}\". Konfliktní událost je \"%{EventTitle}\" a začíná %{StartDate}.";
|
||||
@@ -6,7 +6,6 @@ vevent_class2 = "(Hemmelig begivenhed)";
|
||||
vtodo_class0 = "(Offentlig opgave)";
|
||||
vtodo_class1 = "(Privat opgave)";
|
||||
vtodo_class2 = "(Hemmelig opgave)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Begivenheden \"%{Summary}\" blev oprettet";
|
||||
"The event \"%{Summary}\" was deleted" = "Begivenheden \"%{Summary}\" blev slettet";
|
||||
@@ -14,7 +13,6 @@ vtodo_class2 = "(Hemmelig opgave)";
|
||||
"The following attendees(s) were notified" = "Følgende deltager(e) blev underrettet";
|
||||
"The following attendees(s) were added" = "Følgende deltager(e) blev tilføjet";
|
||||
"The following attendees(s) were removed" = "Følgende deltager(e) blev fjernet";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalender";
|
||||
"startDate_label" = "Start";
|
||||
@@ -23,20 +21,17 @@ vtodo_class2 = "(Hemmelig opgave)";
|
||||
"location_label" = "Sted";
|
||||
"summary_label" = "Resumé:";
|
||||
"comment_label" = "Kommentér:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Begivenhed Invitation: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(Sendt af%{SentBy})";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}har inviteret dig til %{Summary}.\n\nStart: %{StartDate}\nSlut: %{EndDate}\nBeskrivelse: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer}%{SentByText} har inviteret dig til%{Summary}.\n\nStart:%{StartDate}ved%{StartTime}\nEnd:%{EndDate}ved%{endTime}\nDescription:%{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Begivenhed Annulleret: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}har aflyst denne begivenhed: %{Summary}.\n\nStart: %{StartDate}\nSlut: %{EndDate}\nBeskrivelse: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}har annulleret denne begivenhed: %{Summary}.\n\nStart: %{StartDate} %{StartTime}\nSlut: %{EndDate} %{EndTime}\nBeskrivelse: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Aftalen \"%{Summary}\" for %{OldStartDate} er ændret";
|
||||
@@ -46,7 +41,6 @@ vtodo_class2 = "(Hemmelig opgave)";
|
||||
= "Følgende parametre har ændret sig i \"%{Summary}\" møde:";
|
||||
"Please accept or decline those changes."
|
||||
= "Venligst acceptér eller afvis disse ændringer.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Accepterede invitationen: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Afslåede invitationen: \"%{Summary}\"";
|
||||
@@ -60,7 +54,6 @@ vtodo_class2 = "(Hemmelig opgave)";
|
||||
= "%{Attendee} %{SentByText} har delegeret en invitation til %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText} har endnu ikke taget stilling til din begivenhedsinvitation.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kan ikke tilgå ressourcen: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimalt antal tilladte indkaldelser (%{NumberOfSimultaneousBookings}) er nået for ressource \"%{Cn} %{SystemEmail}\". Konflikter med begivenheden \"%{EventTitle}\", som starter %{StartDate}.";
|
||||
@@ -1,13 +1,12 @@
|
||||
"Inviting the following persons is prohibited:" = "De volgende personen uitnodigen is niet toegestaan:";
|
||||
"Inviting the following persons is prohibited:" = "Uitnodigen van de volgende personen is niet toegestaan:";
|
||||
"Personal Calendar" = "Persoonlijke agenda";
|
||||
vevent_class0 = "(Publieke afspraak)";
|
||||
vevent_class1 = "(Privé-afspraak)";
|
||||
vevent_class2 = "(Vertrouwelijke afspraak)";
|
||||
vevent_class1 = "(Privé-gebeurtenis)";
|
||||
vevent_class2 = "(Vertrouwelijke gebeurtenis)";
|
||||
|
||||
vtodo_class0 = "(Publieke taak)";
|
||||
vtodo_class1 = "(Privétaak)";
|
||||
vtodo_class2 = "(Vertrouwelijke taak)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "De gebeurtenis \"%{Summary}\" is aangemaakt";
|
||||
"The event \"%{Summary}\" was deleted" = "De gebeurtenis \"%{Summary}\" is verwijderd";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
|
||||
"The following attendees(s) were notified" = "De volgende deelnemers zijn in kennis gesteld";
|
||||
"The following attendees(s) were added" = "De volgende deelnemers zijn toegevoegd";
|
||||
"The following attendees(s) were removed" = "De volgende deelnemers zijn verwijderd";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Agenda";
|
||||
"startDate_label" = "Start";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Vertrouwelijke taak)";
|
||||
"location_label" = "Locatie";
|
||||
"summary_label" = "Samenvatting:";
|
||||
"comment_label" = "Commentaar:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Uitnodiging voor gebeurtenis: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(Verzonden door %{SentBy})";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}heeft u uitgenodigd voor: %{Summary}.\n\nBegin: %{StartDate}\nEinde: %{EndDate}\nBeschrijving: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}heeft u uitgenodigd voor %{Summary}.\n\nStart: %{StartDate} om %{StartTime}\nEinde: %{EndDate} om %{EndTime}\nOmschrijving: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Gebeurtenis geannuleerd: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}heeft deze gebeurtenis afgezegd: %{Summary}.\n\nBegin: %{StartDate}\nEinde: %{EndDate}\nBeschrijving: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}heeft de gebeurtenis: %{Summary} geannuleerd.\n\nStart: %{StartDate} om %{StartTime}\nEinde: %{EndDate} om %{EndTime}\nOmschrijving: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "De gebeurtenis \"%{Summary}\" op %{OldStartDate} is veranderd.";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
|
||||
= "De volgende parameters zijn veranderd in de \"%{Summary}\" vergadering:";
|
||||
"Please accept or decline those changes."
|
||||
= "Accepteer deze wijzigingen of wijs ze af.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Uitnodiging aanvaard: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Uitnodiging afgewezen: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
|
||||
= "%{Attendee} %{SentByText}heeft de uitnodiging gedelegeerd aan %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}heeft nog niet gereageerd op uw uitnodiging.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kan bron \"%{Cn} %{SystemEmail}\" niet benaderen";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Het maximale aantal gelijktijdige reserveringen (%{NumberOfSimultaneousBookings}) voor \"%{Cn} %{SystemEmail}\" is bereikt. De conflicterende gebeurtenis is \"%{EventTitle}\" en begint op %{StartDate}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Confidential event)";
|
||||
vtodo_class0 = "(Public task)";
|
||||
vtodo_class1 = "(Private task)";
|
||||
vtodo_class2 = "(Confidential task)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "The event \"%{Summary}\" was created";
|
||||
"The event \"%{Summary}\" was deleted" = "The event \"%{Summary}\" was deleted";
|
||||
@@ -15,29 +14,25 @@ vtodo_class2 = "(Confidential task)";
|
||||
"The following attendees(s) were notified" = "The following attendee(s) were notified";
|
||||
"The following attendees(s) were added" = "The following attendee(s) were added";
|
||||
"The following attendees(s) were removed" = "The following attendee(s) were removed";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Calendar";
|
||||
"startDate_label" = "Start";
|
||||
"endDate_label" = "End";
|
||||
"due_label" = "Due Date:";
|
||||
"due_label" = "Due Date";
|
||||
"location_label" = "Location";
|
||||
"summary_label" = "Summary:";
|
||||
"comment_label" = "Comment:";
|
||||
|
||||
"summary_label" = "Summary";
|
||||
"comment_label" = "Comment";
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Event Invitation: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(sent by %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Event Cancelled: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "The appointment \"%{Summary}\" for the %{OldStartDate} has changed";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Confidential task)";
|
||||
= "The following parameters have changed in the \"%{Summary}\" meeting:";
|
||||
"Please accept or decline those changes."
|
||||
= "Please accept or decline those changes.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Accepted invitation: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Declined invitation: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Confidential task)";
|
||||
= "%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}has not yet decided upon your event invitation.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Cannot access resource: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Luottamuksellinen tapahtuma)";
|
||||
vtodo_class0 = "(Julkinen tehtävä)";
|
||||
vtodo_class1 = "(Yksityinen tehtävä)";
|
||||
vtodo_class2 = "(Luottamuksellinen tehtävä)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Tapahtuma \"%{Summary}\" luotiin";
|
||||
"The event \"%{Summary}\" was deleted" = "Tapahtuma \"%{Summary}\" poistettiin";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
|
||||
"The following attendees(s) were notified" = "Seuraava(t) osallistuja(t) on informoitu";
|
||||
"The following attendees(s) were added" = "Seuraava(t) osalllistuja(t) on lisätty";
|
||||
"The following attendees(s) were removed" = "Seuraava(t) osalllistuja(t) poistettiin";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalenteri";
|
||||
"startDate_label" = "Alkaa";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
|
||||
"location_label" = "Sijainti";
|
||||
"summary_label" = "Yhteenveto:";
|
||||
"comment_label" = "Kommentti:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Tapahtumakutsu: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(lähettäjä %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}on kutsunut sinut %{Summary}n.⏎ ⏎ Alkaa: %{StartDate}⏎ Loppuu: %{EndDate}⏎ Kuvaus: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}on kutsunut sinut %{Summary}n.⏎ ⏎ Alkaa: %{StartDate} klo %{StartTime}⏎ Loppuu: %{EndDate} klo %{EndTime}⏎ Kuvaus: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Tapahtuma peruttu: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}on perunut tapahtuman: %{Summary}.⏎ ⏎ Alkaa: %{StartDate}⏎ Loppuu: %{EndDate}⏎ Kuvaus: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}on peruuttanut tapahtuman: %{Summary}.⏎ ⏎ Alkaa: %{StartDate}klot %{StartTime}⏎ Loppuu: %{EndDate} klo %{EndTime}⏎ Kuvaus: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Tapaaminen \"%{Summary}\" %{OldStartDate} on muuttunut";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
|
||||
= "Seuraavat tiedot ovat muuttuneet tapaamisessa \"%{Summary}\":";
|
||||
"Please accept or decline those changes."
|
||||
= "Ole hyvä ja hyväksy tai hylkää muutokset.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Hyväksytty kutsu: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Hylätty kutsu: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
|
||||
= "%{Attendee} %{SentByText} on lähettänyt kutsusi edelleen osoitteeseen %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText} ei vielä ole tehnyt päätöstä tapahtumakutsustasi.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ei pääsyä resurssiin: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimimäärä yhtäaikaisia varauksia\n(%{NumberOfSimultaneousBookings}) saavutettu resurssille \"%{Cn} %{SystemEmail}\". Ristiriitainen tapahtuma: \"%{EventTitle}\" alkaa %{StartDate}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Événement confidentiel)";
|
||||
vtodo_class0 = "(Tâche publique)";
|
||||
vtodo_class1 = "(Tâche privée)";
|
||||
vtodo_class2 = "(Tâche confidentielle)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "L'événement «%{Summary}» a été créé";
|
||||
"The event \"%{Summary}\" was deleted" = "L'événement «%{Summary}» a été effacé";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Tâche confidentielle)";
|
||||
"The following attendees(s) were notified" = "Les invités suivants ont été avisés ";
|
||||
"The following attendees(s) were added" = "Les invités suivants ont été ajoutés ";
|
||||
"The following attendees(s) were removed" = "Les invités suivants ont été supprimés ";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Agenda ";
|
||||
"startDate_label" = "Début ";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Tâche confidentielle)";
|
||||
"location_label" = "Lieu ";
|
||||
"summary_label" = "Titre :";
|
||||
"comment_label" = "Description :";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Invitation à la réunion : «%{Summary}»";
|
||||
"(sent by %{SentBy}) " = "(envoyé par %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vous a invité à la réunion « %{Summary} ».\n\nDébut: %{StartDate}\nFin: %{EndDate}\nDescription: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vous a invité à la réunion « %{Summary} ».\n\nDébut: %{StartDate} à %{StartTime}\nFin: %{EndDate} à %{EndTime}\nDescription: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Réunion annulée : « %{Summary} »";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}a annulé cette réunion : « %{Summary} ».\n\nDébut: %{StartDate}\nFin: %{EndDate}\nDescription: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}a annulé cette réunion : « %{Summary} ».\n\nDébut: %{StartDate} à %{StartTime}\nFin: %{EndDate} à %{EndTime}\nDescription: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "La réunion « %{Summary} » de %{OldStartDate} a été modifiée";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tâche confidentielle)";
|
||||
= "Les paramètres suivants ont été modifiés pour la réunion « %{Summary} » :";
|
||||
"Please accept or decline those changes."
|
||||
= "Veuillez reconfirmer ou annuler votre présence.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Invitation acceptée : «%{Summary}»";
|
||||
"Declined invitation: \"%{Summary}\"" = "Invitation refusée : «%{Summary}»";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tâche confidentielle)";
|
||||
= "%{Attendee} %{SentByText}a délégué votre invitation à %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}choisit de reporter sa décision par rapport à votre invitation.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Impossible d'accéder à la ressource suivante: %{Cn} %{SystemEmail}";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Le nombre maximal de réservations simultanées (%{NumberOfSimultaneousBookings}) est atteint pour la ressource «%{Cn} %{SystemEmail}». L'événement en conflit est «%{EventTitle}» et débute le %{StartDate}.";
|
||||
@@ -54,7 +54,7 @@ Appointments_RESOURCE_FILES += \
|
||||
MSExchangeFreeBusySOAPResponseMap.plist \
|
||||
MSExchangeFreeBusySOAPRequest.wo
|
||||
|
||||
Appointments_LANGUAGES = Arabic Basque BrazilianPortuguese Catalan ChineseTaiwan Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian NorwegianBokmal NorwegianNynorsk Polish Russian Slovak Slovenian SpanishSpain SpanishArgentina Swedish Ukrainian Welsh
|
||||
Appointments_LANGUAGES = Arabic Basque BrazilianPortuguese Catalan ChineseTaiwan Croatian Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian Lithuanian Macedonian NorwegianBokmal NorwegianNynorsk Polish Portuguese Russian Slovak Slovenian SpanishSpain SpanishArgentina Swedish Ukrainian Welsh
|
||||
|
||||
Appointments_LOCALIZED_RESOURCE_FILES = Localizable.strings
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Vertraulicher Termin)";
|
||||
vtodo_class0 = "(Öffentliche Aufgabe)";
|
||||
vtodo_class1 = "(Private Aufgabe)";
|
||||
vtodo_class2 = "(Vertrauliche Aufgabe)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Der Termin \"%{Summary}\" wurde angelegt";
|
||||
"The event \"%{Summary}\" was deleted" = "Der Termin \"%{Summary}\" wurde gelöscht";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
|
||||
"The following attendees(s) were notified" = "Die folgenden Teilnehmer wurden benachrichtigt";
|
||||
"The following attendees(s) were added" = "Die folgenden Teilnehmer wurden hinzugefügt";
|
||||
"The following attendees(s) were removed" = "Die folgenden Teilnehmer wurden entfernt";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalender";
|
||||
"startDate_label" = "Beginn";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
|
||||
"location_label" = "Ort";
|
||||
"summary_label" = "Zusammenfassung:";
|
||||
"comment_label" = "Kommentar:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Termineinladung: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(gesendet von %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} Hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate} um %{StartTime}\nEnde: %{EndDate} um %{EndTime}\nBeschreibung: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Termin abgesagt: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} hat den folgenden Termin abgesagt: \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} hat diesen Termin abgesagt: \"%{Summary}\".\n\nBeginn: %{StartDate} um %{StartTime}\nEnde: %{EndDate} um %{EndTime}\nBeschreibung: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Die Verabredung \"%{Summary}\" am %{OldStartDate} wurde geändert";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
|
||||
= "Folgendes wurde am Termin \"%{Summary}\" geändert:";
|
||||
"Please accept or decline those changes."
|
||||
= "Bitte akzeptieren Sie diese Änderung oder lehnen Sie diese ab.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Einladung zugestimmt: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Einladung abgelehnt: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
|
||||
= "%{Attendee} %{SentByText} hat die Einladung delegiert an %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText} hat noch nicht über Ihre Termineinladung entschieden.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kann auf folgende Ressource nicht zugreifen: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Die maximale Anzahl von gleichzeitigen Buchungen (%{NumberOfSimultaneousBookings}) für die Ressource \"%{Cn} %{SystemEmail}\" ist erreicht. Der kollidierende Termin ist \"%{EventTitle}\", und beginnt am %{StartDate}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Bizalmas esemény)";
|
||||
vtodo_class0 = "(Nyilvános feladat)";
|
||||
vtodo_class1 = "(Magán feladat)";
|
||||
vtodo_class2 = "(Bizalmas feladat)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "A \"%{Summary}\" esemény létre lett hozva";
|
||||
"The event \"%{Summary}\" was deleted" = "A \"%{Summary}\" esemény törölve lett";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Bizalmas feladat)";
|
||||
"The following attendees(s) were notified" = "Az alábbi résztvevők lettek értesítve";
|
||||
"The following attendees(s) were added" = "Az alábbi résztvevők lettek hozzáadva";
|
||||
"The following attendees(s) were removed" = "Az alábbi résztvevők lettek eltávolítva";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Naptár";
|
||||
"startDate_label" = "Kezdete";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Bizalmas feladat)";
|
||||
"location_label" = "Hely";
|
||||
"summary_label" = "Összegzés:";
|
||||
"comment_label" = "Megjegyzés:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Meghívás eseményre: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(%{SentBy} által elküldve) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} meghívta Önt a(z) %{Summary} tárgyú eseményre.\n\nKezdete: %{StartDate}: \nVége: %{EndDate}\nLeírása: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} meghívta Önt erre az eseményre: %{Summary}.\n\nKezdete: %{StartDate} at %{StartTime}\nVége: %{EndDate} at %{EndTime}\nLeírás: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Az esemény törölve lett: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} törlte az alábbi eseményt: %{Summary}.\n\nKzdete: %{StartDate}\nVége: %{EndDate}\nLeírás: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} törölte az alábbi eseményt: %{Summary}.\n\nKezdete: %{StartDate} at %{StartTime}\nVége: %{EndDate} at %{EndTime}\nLeírás: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "A(z) \"%{Summary}\" tárgyú, %{OldStartDate} időpontra tervezett esemény módosult.";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Bizalmas feladat)";
|
||||
= "A \"%{Summary}\" tárgyú találkozó következő adatai módosultak:";
|
||||
"Please accept or decline those changes."
|
||||
= "Kérem fogadja el vagy utasítsa vissza a változásokat.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Elfogadott meghivás: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Elutasított meghívás: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Bizalmas feladat)";
|
||||
= "%{Attendee} %{SentByText} átruházta a meghívást az alábbi személynek %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}még meggondolja a meghívását.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Az alábbi elem nem elérhető: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "A maximális egyidejű foglalás számát (%{NumberOfSimultaneousBookings}) elérte a(z) \"%{Cn} %{SystemEmail}\" elem esetében. Az ütköző esemény a(z) \"%{EventTitle}\", a kezdeti időpontja %{StartDate}.";
|
||||
@@ -6,24 +6,19 @@ vevent_class2 = "(Viðburður er trúnaðarmál)";
|
||||
vtodo_class0 = "(Almennt verkefni)";
|
||||
vtodo_class1 = "(Einkaverkefni)";
|
||||
vtodo_class2 = "(Verkefni er trúnaðarmál)";
|
||||
|
||||
/* Receipts */
|
||||
"Title:" = "Titill:";
|
||||
"Start:" = "Byrjun:";
|
||||
"End:" = "Endir:";
|
||||
|
||||
"Receipt: users invited to a meeting" = "Móttakandi: notendur boðnir á fund";
|
||||
"You have invited the following attendees(s):" = "Eftirfarandi þáttakanda/þáttakendum hefur verið boðið:";
|
||||
"... to attend the following event:" = "... að vera viðstaddir eftirfarandi viðburð:";
|
||||
|
||||
"Receipt: invitation updated" = "Móttakandi: uppfærður boðsmiði";
|
||||
"The following attendees(s):" = "Eftirfarandi þáttakandi/Þáttakendur:";
|
||||
"... have been notified of the changes to the following event:" = "... hafa fengið upplýsingar um breytingarnar á eftirfarandi viðburði:";
|
||||
|
||||
"Receipt: attendees removed from an event" = "Móttakandi: þáttakendur fjarlægðir af viðburði";
|
||||
"You have removed the following attendees(s):" = "Eftirfarandi þáttakandi/þáttakendur hafa verið fjarlægðir:";
|
||||
"... from the following event:" = "... af eftirfarandi viðburði:";
|
||||
|
||||
/* IMIP messages */
|
||||
"startDate_label" = "Byrjun";
|
||||
"endDate_label" = "Endir";
|
||||
@@ -31,17 +26,14 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
|
||||
"location_label" = "Staðsetning";
|
||||
"summary_label" = "Samantekt:";
|
||||
"comment_label" = "Athugasemd:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Boð á viðburð: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(%{SentBy} sendi þetta) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}hefur boðið þér á þennan viðburð: %{Summary}.\n\nByrjun: %{StartDate} at %{StartTime}\nEndir: %{EndDate} at %{EndTime}\nLýsing: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Viðburði aflýst: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}hefur aflýst þessum viðburði: %{Summary}.\n\nByrjun: %{StartDate} kl. %{StartTime}\nEndir: %{EndDate} kl. %{EndTime}\nLýsing: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
|
||||
= "Tímapöntunin \"%{Summary}\" sem var þann %{OldStartDate} kl. %{OldStartTime} hefur breyst.";
|
||||
@@ -49,7 +41,6 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
|
||||
= "Eftirfarandi atriði hafa breyst varðandi fundinn: \"%{Summary}\"";
|
||||
"Please accept or decline those changes."
|
||||
= "Þessar breytingar þarf að samþykkja eða hafna.";
|
||||
|
||||
/* Reply */
|
||||
"%{Attendee} %{SentByText}has accepted your event invitation."
|
||||
= "%{Attendee} %{SentByText}hefur samþykkt boð þitt á viðburð.";
|
||||
@@ -59,6 +50,5 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
|
||||
= "%{Attendee} %{SentByText}hefur skipað sem fulltrúa sinn %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}hefur ekki enn tekið ákvörðun varðandi boð þitt.";
|
||||
|
||||
/* Resources */
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Hámarksfjöldi samtímabókana (%{NumberOfSimultaneousBookings}) hefur verið náð varðandi aðfangið \"%{Cn} %{SystemEmail}\".";
|
||||
Loaded 100 of 675 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user