Compare commits
293
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29f2d960a7 | ||
|
|
225fe847a7 | ||
|
|
720b1d5ab5 | ||
|
|
28bd4ca318 | ||
|
|
c6dbcb2f7c | ||
|
|
5dfbfe827d | ||
|
|
6ddb40fd2b | ||
|
|
37f29c06ea | ||
|
|
248892c394 | ||
|
|
b4e4b9c29f | ||
|
|
e94b4416f8 | ||
|
|
8d19ec4aff | ||
|
|
980db7a980 | ||
|
|
c5c6ff33d8 | ||
|
|
7e1bbe34dc | ||
|
|
f9884e3f55 | ||
|
|
15ab0f851d | ||
|
|
1f98d7cced | ||
|
|
2755a83e65 | ||
|
|
9936477f07 | ||
|
|
8c0ef9cf50 | ||
|
|
8456d49686 | ||
|
|
5a3cafbbb0 | ||
|
|
dc085789e7 | ||
|
|
bbdb27e75e | ||
|
|
1279629e86 | ||
|
|
f7d748ea98 | ||
|
|
2d1a4b320c | ||
|
|
8df311547f | ||
|
|
f42d6f5f4a | ||
|
|
01642eca3e | ||
|
|
2ae8c6f8b2 | ||
|
|
31deae11b4 | ||
|
|
19676593ea | ||
|
|
4a27beabdd | ||
|
|
8dae5faaa1 | ||
|
|
85ce41dc68 | ||
|
|
7015713741 | ||
|
|
7644d6fb4d | ||
|
|
4b8a9bcc3b | ||
|
|
a9984d9487 | ||
|
|
2a90102a81 | ||
|
|
fbc73dc3bc | ||
|
|
1d78b2df96 | ||
|
|
6aef713d28 | ||
|
|
71db3945cb | ||
|
|
da230410be | ||
|
|
82114f75aa | ||
|
|
f8d31a6413 | ||
|
|
707aabde15 | ||
|
|
ceec497aec | ||
|
|
46f93d8629 | ||
|
|
f2347e13d7 | ||
|
|
106f8ec314 | ||
|
|
f58554a700 | ||
|
|
76b8de1805 | ||
|
|
64456c10e5 | ||
|
|
ef7de7c10d | ||
|
|
106c6a24ae | ||
|
|
863f182e84 | ||
|
|
ba37353e42 | ||
|
|
1915748e33 | ||
|
|
b170ae866f | ||
|
|
7c69e37449 | ||
|
|
7f26d47cd1 | ||
|
|
b55ec52bfe | ||
|
|
f919df050a | ||
|
|
c2e4f7441d | ||
|
|
ca27464984 | ||
|
|
6cbc5573a1 | ||
|
|
e4fae417b9 | ||
|
|
eda84c7b5d | ||
|
|
9d31023724 | ||
|
|
41e80bb105 | ||
|
|
e428e74e6c | ||
|
|
7a5e550510 | ||
|
|
0605fdf02a | ||
|
|
d3290dbb8d | ||
|
|
34f626b84c | ||
|
|
e173c6d621 | ||
|
|
1425bb1c75 | ||
|
|
5b65eaa572 | ||
|
|
892bd693d7 | ||
|
|
6812cb08dc | ||
|
|
b62167dd00 | ||
|
|
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 | ||
|
|
6f2eae5d67 | ||
|
|
9a7ea26b80 | ||
|
|
38c9a229c0 | ||
|
|
e75ec09445 | ||
|
|
58d03eedc3 | ||
|
|
98ad8f1c0b | ||
|
|
37b817cd68 | ||
|
|
9fd8f3edef | ||
|
|
ce11aca2bd | ||
|
|
861057f22f | ||
|
|
b910501afa | ||
|
|
ba68bd8935 | ||
|
|
063f878199 | ||
|
|
6bccd2eea0 | ||
|
|
652ae763d4 | ||
|
|
734aba5ddb | ||
|
|
3da6c90bbe | ||
|
|
21a6480834 | ||
|
|
42f10e1929 | ||
|
|
03b706e1d8 | ||
|
|
2be556299e | ||
|
|
b075a306c6 | ||
|
|
36d8fa12d3 | ||
|
|
07092828fe | ||
|
|
7c0ba7b72b | ||
|
|
750a629df8 | ||
|
|
33a1d2c397 | ||
|
|
2e75972df6 | ||
|
|
1fdb44b0a7 | ||
|
|
3868ce0fb1 | ||
|
|
fc82eb79f6 | ||
|
|
9425868365 | ||
|
|
6280e4ded6 | ||
|
|
f19074334c | ||
|
|
b702e40ebb | ||
|
|
190f27ed7c | ||
|
|
cbc3e3e97d | ||
|
|
361d5ea3a6 | ||
|
|
24e3071e24 | ||
|
|
1f29198495 | ||
|
|
916442f9cf | ||
|
|
47859b76d6 | ||
|
|
3a60b6e38e | ||
|
|
ae3ac0a09a | ||
|
|
27a19ac956 | ||
|
|
27b9b7bfa7 | ||
|
|
18f758cb0a | ||
|
|
321672e2c3 | ||
|
|
8d9b54815c | ||
|
|
1dbfc18379 | ||
|
|
05b330e11b | ||
|
|
307876e6c7 | ||
|
|
ac6b299988 | ||
|
|
234db671ad | ||
|
|
2d33b01f31 | ||
|
|
490354427d | ||
|
|
22ce38808a | ||
|
|
82aca2cfe5 | ||
|
|
b17488ff25 | ||
|
|
ff3557598f | ||
|
|
26bd1b30fa | ||
|
|
0ce04fd4b6 | ||
|
|
f3656f7571 | ||
|
|
2aba083147 | ||
|
|
9414df26c5 | ||
|
|
7496b92da5 | ||
|
|
e38b0ac64f | ||
|
|
1404dbdb88 | ||
|
|
3dc8cc78ea | ||
|
|
72cbd9a45f | ||
|
|
10ac445f7c | ||
|
|
c5b5a4243d | ||
|
|
304ffa2099 | ||
|
|
e4ad255fc2 | ||
|
|
390a853471 | ||
|
|
dd9b5e18b6 | ||
|
|
5c017c19fc | ||
|
|
dec152e8e5 | ||
|
|
4922e6004d | ||
|
|
b91972d1dc | ||
|
|
5c3cdda186 | ||
|
|
4b19e64071 | ||
|
|
b6dee056ca | ||
|
|
372247a643 | ||
|
|
6830d6d930 | ||
|
|
366ad44cd1 | ||
|
|
9430f40101 | ||
|
|
b7c2d8ae95 | ||
|
|
f03e7a6604 | ||
|
|
4e7a3ce5fa | ||
|
|
61c542cb95 | ||
|
|
d2640e1501 | ||
|
|
20b91cc7bf | ||
|
|
b05f7a5beb | ||
|
|
3a61b7bae4 | ||
|
|
7bde181d3f | ||
|
|
1336a1d5a8 | ||
|
|
cf606e3224 | ||
|
|
c9f2fe2f85 | ||
|
|
10bc15d41d | ||
|
|
393b6d5128 | ||
|
|
68ae978b13 | ||
|
|
f4e3b98042 | ||
|
|
cfab18e1b8 | ||
|
|
959b4e015d | ||
|
|
7c6c78d13b | ||
|
|
7db109525d | ||
|
|
2ce85f6c5c | ||
|
|
7d2f96e96b | ||
|
|
3bf7afdfb7 | ||
|
|
503a70118f | ||
|
|
928b6b596e | ||
|
|
d0d93a297b | ||
|
|
936c38c43e | ||
|
|
7da7bdc1bf | ||
|
|
1e101ef01a | ||
|
|
3171257ef4 | ||
|
|
2c8d65843f | ||
|
|
62b16f4ef3 | ||
|
|
b86b1afdb9 | ||
|
|
8e047aaa9c | ||
|
|
dc2cc14d25 | ||
|
|
3f1a43cc13 | ||
|
|
67d0d95869 | ||
|
|
d16bf7c1a7 | ||
|
|
b527099907 | ||
|
|
72aab7f83f | ||
|
|
dfecce7381 | ||
|
|
82798ccb99 | ||
|
|
3809c002ed | ||
|
|
d9b4abc2f1 | ||
|
|
c1b0fd53dc | ||
|
|
c0a22ead1d | ||
|
|
d8e37cd7d8 | ||
|
|
7540cc3e33 | ||
|
|
d92b0341fb | ||
|
|
5e84f2817f | ||
|
|
c61eca27a1 | ||
|
|
96d88de564 | ||
|
|
90e11d76f6 | ||
|
|
3dd8bddc0e | ||
|
|
95f11f6d0d | ||
|
|
1ec53a063c | ||
|
|
cab5846e37 | ||
|
|
76f80c507c | ||
|
|
afed233131 | ||
|
|
b04b66fd79 | ||
|
|
13f2ac9cbb | ||
|
|
7dc333e790 | ||
|
|
cc62576fef | ||
|
|
a1d07c59af | ||
|
|
0a37652dd6 | ||
|
|
725dea5f6f | ||
|
|
76dd53b461 | ||
|
|
020c895925 | ||
|
|
f4963df50f | ||
|
|
7160d94e91 |
No files matched your search
+13
-28
@@ -1,33 +1,18 @@
|
||||
config.make
|
||||
tags
|
||||
*._*
|
||||
*.css
|
||||
*.pyc
|
||||
*.sax
|
||||
*.swp
|
||||
*.wox/
|
||||
*/*/*.SOGo/
|
||||
*/*/*/obj/
|
||||
*/*/obj/
|
||||
*/obj/
|
||||
.scss-lint-config.yml_
|
||||
Documentation/*.docbook
|
||||
Documentation/*.pdf
|
||||
*._*
|
||||
*/*/obj/
|
||||
*/*/*/obj/
|
||||
*/*/*.SOGo/
|
||||
*.sax
|
||||
*.wox/
|
||||
*.swp
|
||||
SoObjects/SOGo/SOGo.framework/
|
||||
SoObjects/SOGo/derived_src/
|
||||
Tests/*/config.py
|
||||
UI/WebServerResources/.bowerrc
|
||||
UI/WebServerResources/.sass-cache/
|
||||
UI/WebServerResources/bower_components/
|
||||
UI/WebServerResources/css/
|
||||
UI/WebServerResources/css/styles.css
|
||||
UI/WebServerResources/css/styles.css.map
|
||||
UI/WebServerResources/js/Common.js*
|
||||
UI/WebServerResources/js/Contacts.js*
|
||||
UI/WebServerResources/js/Mailer.js*
|
||||
UI/WebServerResources/js/Preferences.js*
|
||||
UI/WebServerResources/js/Scheduler.js*
|
||||
UI/WebServerResources/js/vendor/
|
||||
UI/WebServerResources/node_modules/
|
||||
UI/WebServerResources/scss/.sass-cache/
|
||||
config.make
|
||||
doc
|
||||
tags
|
||||
*.pyc
|
||||
._*
|
||||
Documentation/*.docbook
|
||||
Documentation/*.pdf
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "UI/WebServerResources/angular-material"]
|
||||
path = UI/WebServerResources/angular-material
|
||||
url = https://github.com/angular/material
|
||||
+22
@@ -18,10 +18,12 @@ trans.fr = UI/MailerUI/French.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.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
|
||||
@@ -47,10 +49,12 @@ trans.fr = UI/PreferencesUI/French.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.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
|
||||
@@ -76,10 +80,12 @@ trans.fr = UI/Scheduler/French.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.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
|
||||
@@ -105,10 +111,12 @@ trans.fr = UI/Contacts/French.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.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
|
||||
@@ -134,10 +142,12 @@ trans.fr = UI/MainUI/French.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.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
|
||||
@@ -163,10 +173,12 @@ trans.fr = UI/Common/French.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.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
|
||||
@@ -192,10 +204,12 @@ trans.fr = UI/AdministrationUI/French.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.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
|
||||
@@ -221,10 +235,12 @@ trans.fr = SoObjects/Appointments/French.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.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
|
||||
@@ -250,10 +266,12 @@ trans.fr = SoObjects/Contacts/French.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.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
|
||||
@@ -279,10 +297,12 @@ trans.fr = SoObjects/Mailer/French.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.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
|
||||
@@ -308,10 +328,12 @@ trans.fr = UI/MailPartViewers/French.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.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];
|
||||
|
||||
|
||||
+181
-49
@@ -48,11 +48,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@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 +94,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 +177,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 +208,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]];
|
||||
@@ -217,6 +260,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 +270,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 +279,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 +295,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 +348,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 +418,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 +428,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"]))
|
||||
|
||||
@@ -112,13 +112,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,7 +192,9 @@ 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"];
|
||||
|
||||
[[o properties] addEntriesFromDictionary: values];
|
||||
[o save];
|
||||
@@ -159,7 +221,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *nameInCache;
|
||||
|
||||
if (theFolderType == ActiveSyncMailFolder)
|
||||
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]];
|
||||
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
|
||||
else
|
||||
{
|
||||
NSString *component_name;
|
||||
@@ -170,7 +232,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
nameInCache= [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
}
|
||||
|
||||
return nameInCache;
|
||||
@@ -403,8 +465,8 @@ 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:
|
||||
@@ -414,27 +476,27 @@ 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 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>"];
|
||||
@@ -577,36 +639,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;
|
||||
@@ -622,14 +692,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))
|
||||
@@ -656,8 +748,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];
|
||||
|
||||
@@ -673,7 +763,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSDictionary *component;
|
||||
NSArray *allComponents;
|
||||
|
||||
BOOL updated;
|
||||
BOOL updated, initialLoadInProgress;
|
||||
int deleted, return_count;
|
||||
|
||||
if (theFolderType == ActiveSyncContactFolder)
|
||||
@@ -683,13 +773,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
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++)
|
||||
@@ -847,12 +1000,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSMutableArray *allCacheObjects, *sortedBySequence;
|
||||
|
||||
SOGoMailObject *mailObject;
|
||||
NSArray *allMessages;
|
||||
NSArray *allMessages, *a;
|
||||
NSString *firstUIDAdded;
|
||||
|
||||
int j, k, return_count;
|
||||
BOOL found_in_cache;
|
||||
int j, k, return_count, highestmodseq;
|
||||
BOOL found_in_cache, initialLoadInProgress;
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
initialLoadInProgress = NO;
|
||||
found_in_cache = NO;
|
||||
firstUIDAdded = nil;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
highestmodseq = 0;
|
||||
|
||||
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
|
||||
}
|
||||
else
|
||||
{
|
||||
a = [theSyncKey componentsSeparatedByString: @"-"];
|
||||
highestmodseq = [[a objectAtIndex: 1] intValue];
|
||||
}
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||
max = [allMessages count];
|
||||
|
||||
allCacheObjects = [NSMutableArray array];
|
||||
@@ -860,7 +1039,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];
|
||||
@@ -869,15 +1048,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...
|
||||
@@ -892,12 +1138,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;
|
||||
|
||||
@@ -911,20 +1155,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *lastSequence;
|
||||
more_available = YES;
|
||||
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
||||
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: @"%@-%@", 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]];
|
||||
@@ -942,9 +1201,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:\">"];
|
||||
@@ -960,6 +1222,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]])
|
||||
{
|
||||
@@ -992,11 +1257,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]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,16 +1281,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:
|
||||
@@ -1113,15 +1398,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, *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];
|
||||
|
||||
@@ -1144,7 +1430,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];
|
||||
|
||||
@@ -1169,13 +1463,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"]))
|
||||
{
|
||||
@@ -1200,6 +1516,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// By default, send MIME mails. See #3146 for details.
|
||||
if (!bodyPreferenceType)
|
||||
bodyPreferenceType = @"4";
|
||||
|
||||
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
||||
|
||||
if (!mimeSupport)
|
||||
mimeSupport = @"1";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1225,11 +1546,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
|
||||
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]];
|
||||
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||
}
|
||||
}
|
||||
|
||||
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
||||
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
||||
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
|
||||
|
||||
//
|
||||
// We process the commands from the request
|
||||
@@ -1268,10 +1591,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.
|
||||
@@ -1430,19 +1754,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:\">"];
|
||||
@@ -1457,12 +1786,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.
|
||||
@@ -1472,17 +1805,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++)
|
||||
@@ -1492,25 +1828,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
|
||||
{
|
||||
@@ -1518,8 +1891,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,15 +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,20 +153,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@implementation SOGoActiveSyncDispatcher
|
||||
|
||||
static BOOL debugOn = NO;
|
||||
|
||||
- (id) init
|
||||
{
|
||||
[super init];
|
||||
|
||||
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
||||
folderTableURL = nil;
|
||||
imapFolderGUIDS = nil;
|
||||
syncRequest = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(folderTableURL);
|
||||
RELEASE(imapFolderGUIDS);
|
||||
RELEASE(syncRequest);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -169,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];
|
||||
@@ -202,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];
|
||||
|
||||
@@ -229,23 +241,27 @@ static BOOL debugOn = NO;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
NSDictionary *imapGUIDs;
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
||||
|
||||
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
|
||||
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
if (!imapFolderGUIDS)
|
||||
{
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
|
||||
[imapFolderGUIDS retain];
|
||||
|
||||
}
|
||||
|
||||
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
}
|
||||
|
||||
return theIdToTranslate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -691,26 +707,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];
|
||||
|
||||
@@ -824,32 +839,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++)
|
||||
@@ -937,7 +958,9 @@ 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];
|
||||
|
||||
command_count++;
|
||||
@@ -946,17 +969,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];
|
||||
@@ -964,7 +999,6 @@ static BOOL debugOn = NO;
|
||||
}
|
||||
|
||||
count = [folders count]-1;
|
||||
NSString *operation;
|
||||
|
||||
for (fi = 0; fi <= count ; fi++)
|
||||
{
|
||||
@@ -1022,7 +1056,9 @@ 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];
|
||||
@@ -1045,7 +1081,9 @@ 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];
|
||||
@@ -1204,7 +1242,7 @@ static BOOL debugOn = NO;
|
||||
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
||||
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
|
||||
|
||||
count = [allMessages count];
|
||||
|
||||
@@ -1254,7 +1292,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;
|
||||
@@ -1367,6 +1405,8 @@ 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"];
|
||||
|
||||
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
||||
|
||||
@@ -2024,13 +2064,13 @@ static BOOL debugOn = NO;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2094,6 +2134,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/">
|
||||
@@ -2410,39 +2567,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)];
|
||||
@@ -2619,6 +2837,11 @@ static BOOL debugOn = NO;
|
||||
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
||||
|
||||
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] 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.
|
||||
@@ -2934,9 +3157,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
|
||||
{
|
||||
@@ -3026,8 +3265,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)
|
||||
{
|
||||
|
||||
@@ -472,18 +472,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 +522,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 +570,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];
|
||||
@@ -656,16 +690,18 @@ 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, nativeBodyType;
|
||||
uint32_t v;
|
||||
|
||||
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||
|
||||
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
||||
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
@@ -862,7 +898,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 +917,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)
|
||||
{
|
||||
@@ -981,9 +1020,12 @@ struct GlobalObjectId {
|
||||
{
|
||||
[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];
|
||||
|
||||
@@ -1001,7 +1043,8 @@ struct GlobalObjectId {
|
||||
|
||||
// Attachments -namespace 16
|
||||
attachmentKeys = [self fetchFileAttachmentKeys];
|
||||
if ([attachmentKeys count])
|
||||
|
||||
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -1071,9 +1114,15 @@ 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] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
else
|
||||
reference = [[value componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
|
||||
if ([reference length] > 0)
|
||||
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference 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;
|
||||
|
||||
@@ -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,16 @@ 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>
|
||||
|
||||
#include "iCalAlarm+ActiveSync.h"
|
||||
#include "iCalRecurrenceRule+ActiveSync.h"
|
||||
@@ -98,10 +105,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 +124,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,9 +134,6 @@ 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]];
|
||||
|
||||
@@ -226,9 +233,9 @@ 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])
|
||||
if (![self recurrenceId] && [[self uid] length])
|
||||
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
|
||||
|
||||
|
||||
// Sensitivity
|
||||
if ([[self accessClass] isEqualToString: @"PRIVATE"])
|
||||
v = 2;
|
||||
@@ -251,7 +258,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])
|
||||
@@ -265,7 +271,74 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// Recurrence rules
|
||||
if ([self isRecurrent])
|
||||
{
|
||||
NSMutableArray *components, *exdates;
|
||||
iCalEvent *current_component;
|
||||
NSString *recurrence_id;
|
||||
|
||||
unsigned int count, max, i;
|
||||
|
||||
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
|
||||
|
||||
components = [NSMutableArray arrayWithArray: [[self parent] events]];
|
||||
max = [components count];
|
||||
|
||||
if (max > 1 || [self hasExceptionDates])
|
||||
{
|
||||
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>"];
|
||||
}
|
||||
}
|
||||
|
||||
// Comment
|
||||
@@ -285,13 +358,14 @@ 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>"];
|
||||
}
|
||||
}
|
||||
|
||||
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
|
||||
if (![self recurrenceId])
|
||||
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -337,13 +411,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 +436,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 +507,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 +519,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 +583,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 +761,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"]];
|
||||
|
||||
@@ -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>"];
|
||||
}
|
||||
|
||||
+3
-2
@@ -70,8 +70,8 @@ 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>
|
||||
|
||||
+5655
File diff suppressed because it is too large.
Load diff
@@ -1,157 +0,0 @@
|
||||
https://wiki.inverse.ca/focus/SOGo/CodingStyle
|
||||
https://wiki.inverse.ca/focus/SOGo/D%C3%A9veloppement
|
||||
https://wiki.inverse.ca/focus/PacketFence/Devel/BestPractices
|
||||
|
||||
UI and UX
|
||||
---------
|
||||
|
||||
* http://www.google.com/design/spec/material-design/
|
||||
* http://goodui.org/
|
||||
* Try Repeating Your Primary Action instead of showing it just once.
|
||||
* Try Distinct Clickable/Selected Styles instead of blurring them.
|
||||
* Try Undos instead of prompting for confirmation.
|
||||
* Try More Contrast instead of similarity.
|
||||
* Try Exposing Options instead of hiding them.
|
||||
* Try Showing State instead of being state agnostic.
|
||||
* Try Direct Manipulation instead of contextless menus.
|
||||
* Try Exposing Fields instead of creating extra pages.
|
||||
* Try Transitions instead of showing changes instantly.
|
||||
* Try Fewer Borders instead of wasting attention.
|
||||
* Try Designing For Zero Data instead of just data heavy cases.
|
||||
* Try Consistency instead of making people relearn.
|
||||
* Try Conventions instead of reinventing the wheel.
|
||||
* Try Visual Hierarchy instead of dullness.
|
||||
* Try Grouping Related Items instead of disordering.
|
||||
* Try Inline Validation instead of delaying errors.
|
||||
* Try Forgiving Inputs instead of being strict with data.
|
||||
* Try Bigger Click Areas instead of tiny ones.
|
||||
* Try Faster Load Times instead of making people wait.
|
||||
* Try Keyboard Shortcuts instead of buttons only.
|
||||
* Try Upfront Progress instead of starting with a blank.
|
||||
* Try Softer Prompts instead of modal windows.
|
||||
* Try Multifunctional Controls instead of more parts.
|
||||
* Try Icon Labels instead of opening for interpretation.
|
||||
* http://designmodo.com/create-style-guides/
|
||||
* http://uxmyths.com/
|
||||
|
||||
Frameworks & Tools
|
||||
------------------
|
||||
|
||||
GNUstep
|
||||
~~~~~~~
|
||||
|
||||
SOPE
|
||||
~~~~
|
||||
|
||||
AngularJS
|
||||
~~~~~~~~~
|
||||
|
||||
* https://angularjs.org/
|
||||
* http://angular-ui.github.io/ui-router/
|
||||
* https://www.airpair.com/angularjs/posts/angularjs-tutorial
|
||||
|
||||
Angular Material
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* https://material.angularjs.org/
|
||||
|
||||
Lo-Dash
|
||||
~~~~~~~
|
||||
|
||||
* http://lodash.com/
|
||||
* http://underscorejs.org/
|
||||
|
||||
Code Style
|
||||
----------
|
||||
|
||||
Objective-C
|
||||
~~~~~~~~~~~
|
||||
|
||||
* To document the Web APIs, we follow the http://apidocjs.com/[APIDOC] standards.
|
||||
|
||||
apidoc -f ".*\\.m" -i UI -o Documentation/JSON-API
|
||||
|
||||
HTML
|
||||
~~~~
|
||||
|
||||
* Localize your strings:
|
||||
|
||||
<var:string label:value="I'm localized!"/>
|
||||
|
||||
* Localize your attributes:
|
||||
|
||||
<input type="text" label:placeholder="I'm localized!"/>
|
||||
|
||||
* Reuse existing localized strings as much as possible. Otherwise don't forget to update the English Localizable.strings file of the appropriate module.
|
||||
|
||||
JavaScript
|
||||
~~~~~~~~~~
|
||||
|
||||
* http://eslint.org/
|
||||
* https://github.com/toddmotto/angularjs-styleguide
|
||||
* https://github.com/angular/material/blob/master/docs/guides/CODING.md
|
||||
|
||||
* We conform to https://github.com/airbnb/javascript[Airbnb coding standards]. Use https://github.com/jscs-dev/node-jscs[jscs] with the 'airbnb' preset to validate your code style:
|
||||
|
||||
jscs -p airbnb *.js
|
||||
|
||||
* For documentation, we follow the http://usejsdoc.org/[JSDoc3] standards:
|
||||
|
||||
jsdoc UI/WebServerResources/js/{Appointments,Common,Contacts,Mailer}/*.js -d Documentation/JavaScript-API
|
||||
|
||||
Models (facades)
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
////
|
||||
http://trochette.github.io/Angular-Design-Patterns-Best-Practices/
|
||||
////
|
||||
* Move business logic into models and share them.
|
||||
* Keep it simple, separate server interaction and error handling from the model. That way model only handle data processing and code is easier to maintain.
|
||||
|
||||
SASS/CSS
|
||||
~~~~~~~~
|
||||
|
||||
* http://componentcss.com/
|
||||
* https://github.com/styleguide/css
|
||||
* https://smacss.com/
|
||||
|
||||
* For documentation, we follow http://sassdoc.com/[SassDoc] annotations.
|
||||
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
* FlexBox compatibility http://caniuse.com/#feat=flexbox
|
||||
|
||||
Fonts
|
||||
^^^^^
|
||||
|
||||
* We build a custom icons font using the online app https://icomoon.io/app/#/select[icomoon].
|
||||
* http://www.carrois.com/fira-4-0/
|
||||
* Material Design Iconic Font http://zavoloklom.github.io/material-design-iconic-font/icons.html
|
||||
|
||||
Version Control
|
||||
---------------
|
||||
|
||||
* https://devcharm.com/articles/46/improve-your-git-workflow/
|
||||
* Each commit should cover only one thing;
|
||||
* Begin the commit message with a single short (less than 50 characters) line summarizing the change, followed by a blank line and then a more thorough description;
|
||||
* When fixing a bug, commit to the devel branch as well as the maintenance branch of the latest release version (named maintenance/x.y). When a ticket is associated to the bug, add to the description a line saying *Fixes #1234*.
|
||||
* `git pull` may introduce http://stackoverflow.com/questions/15316601/in-what-cases-could-git-pull-be-harmful[inconsistencies and problems]. Replace it with the following alias:
|
||||
|
||||
git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
* https://github.com/angular/protractor
|
||||
* http://karma-runner.github.io/
|
||||
|
||||
JSON API
|
||||
~~~~~~~~
|
||||
|
||||
One practical way to test the JSON API is to use `curl`. To do so, you need to enable `SOGoTrustProxyAuthentication` and configure HTTP authentifcation in Apache. You can pipe the result to [jq](http://stedolan.github.io/jq/) to nicely format and filter the output:
|
||||
|
||||
curl -u username:password http://localhost/SOGo/so/username/Calendar/calendarslist | jq '.'
|
||||
|
||||
curl -u username:password -H 'Content-Type: application/json' -d '{}' http://localhost/SOGo/so/francis/Calendar/personal/71B6-54904400-1-7C308500.ics/save
|
||||
@@ -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");
|
||||
};
|
||||
----
|
||||
|
||||
@@ -233,6 +233,7 @@ Installation
|
||||
This section will guide you through the installation of the native
|
||||
Microsoft Outlook compatibility layer SOGo offers.
|
||||
|
||||
////
|
||||
Red Hat Enterprise Linux v6 x86_64
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -316,7 +317,7 @@ from this guide.
|
||||
On Ubuntu 12.04, the Samba init scripts need to be modified to
|
||||
disable the upstart check. For more details, refer to:
|
||||
https://wiki.samba.org/index.php/Samba4/InitScript
|
||||
|
||||
////
|
||||
|
||||
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -387,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'
|
||||
|
||||
@@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
|
||||
----
|
||||
|
||||
You might consider changing the realm and domain used, to suit your
|
||||
enviroment.
|
||||
environment.
|
||||
|
||||
You might also have to
|
||||
remove `/etc/samba/smb.conf` prior running this command.
|
||||
@@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
|
||||
OpenChange Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
|
||||
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
|
||||
|
||||
First, create the OpenChange MySQL user:
|
||||
|
||||
@@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
|
||||
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
|
||||
----
|
||||
|
||||
////
|
||||
On RHEL, make sure SELinux is disabled:
|
||||
|
||||
setenforce 0
|
||||
////
|
||||
|
||||
Next, you can start Samba using the usual command:
|
||||
|
||||
@@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
|
||||
While HTTPS is not required to access this service, it is strongly
|
||||
recommended.
|
||||
|
||||
////
|
||||
On RHEL-based distributions, the apache configuration required by these
|
||||
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
|
||||
`/etc/httpd/conf.d/rpcproxy.conf`.
|
||||
////
|
||||
|
||||
For Debian-based distributions, these files can be found
|
||||
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
|
||||
@@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
|
||||
a2enconf ocsmanager
|
||||
a2enconf rpcproxy
|
||||
|
||||
////
|
||||
On RHEL-based distributions, make sure the `LoadModule` directive is
|
||||
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
|
||||
////
|
||||
|
||||
The _reqtimeout_ apache module is known to cause problems when using the
|
||||
default configuration shipped with Debian-based systems. On such
|
||||
@@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
|
||||
a2dismod reqtimeout
|
||||
|
||||
You should now restart the Apache service and make sure it will start on
|
||||
boot. On RHEL-based distributions, do:
|
||||
boot.
|
||||
////
|
||||
On RHEL-based distributions, do:
|
||||
|
||||
chkconfig httpd on && /etc/init.d/httpd restart
|
||||
|
||||
////
|
||||
On Debian-based distributions, do:
|
||||
|
||||
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
|
||||
@@ -809,11 +818,10 @@ 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:
|
||||
`python openchange_user_cleanup username`. See the usage output for additional options.
|
||||
`openchange_user_cleanup username`. See the usage output for additional options.
|
||||
* The "Out of Office Assistant" will not currently work. This feature
|
||||
has not been implemented.
|
||||
* Creating folders below INBOX (when not normally permitted by the IMAP
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- TODO have the build system take care of this -->
|
||||
<releaseinfo>Version 2.3.1 - July 2015</releaseinfo>
|
||||
<subtitle>for version 2.3.1</subtitle>
|
||||
<date>2015-07-23</date>
|
||||
<releaseinfo>Version 2.3.4 - December 2015</releaseinfo>
|
||||
<subtitle>for version 2.3.4</subtitle>
|
||||
<date>2015-12-15</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>
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
// TODO have the build system take care of this
|
||||
|
||||
:release_version: 2.3.1
|
||||
:release_version: 2.3.4
|
||||
|
||||
// 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,12 +1,87 @@
|
||||
2.3.x (2015-MM-DD)
|
||||
2.3.4 (2015-12-15)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- initial support for EAS calendar exceptions
|
||||
|
||||
Enhancements
|
||||
- limit the maximum width of toolbar buttons (#3381)
|
||||
|
||||
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)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
|
||||
- improved EAS speed during initial syncing of large mailboxes (#3293)
|
||||
- updated CKEditor to version 4.5.3
|
||||
|
||||
Bug fixes
|
||||
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
|
||||
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- Fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- Folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- Use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
||||
- less sync issues when setting read flag (Zentyal)
|
||||
- attachments with non-latin filenames sent by Outlook are now received (Zentyal)
|
||||
- support attachments from more mail clients (Zentyal)
|
||||
- avoid conflicting message on saving a draft mail (Zentyal)
|
||||
- less conflicting messages in Outlook while moving messages between folders (Zentyal)
|
||||
- start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344)
|
||||
- fixed localization of calendar categories with empty profile (#3295)
|
||||
- fixed options availability in contextual menu of Contacts module (#3342)
|
||||
|
||||
2.3.1 (2015-07-23)
|
||||
------------------
|
||||
@@ -19,6 +94,7 @@ Enhancements
|
||||
- added create-folder subcommand to sogo-tool to create contact and calendar folders
|
||||
- group mail addresses can be used as recipient in Outlook
|
||||
- added 'ActiveSync' module constraints
|
||||
- updated CKEditor to version 4.5.1
|
||||
- added Slovenian translation - thanks to Jens Riecken
|
||||
- added Chinese (Taiwan) translation
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -671,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
|
||||
return newAttachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#import "MAPIStoreDBFolder.h"
|
||||
#import "MAPIStoreDBMessage.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -43,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);
|
||||
@@ -74,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
|
||||
{
|
||||
@@ -104,6 +125,105 @@
|
||||
return objectVersion;
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList
|
||||
{
|
||||
BOOL updated;
|
||||
enum mapistore_error rc;
|
||||
NSData *currentChangeList, *changeKey;
|
||||
NSMutableArray *changeKeys;
|
||||
NSMutableData *newChangeList;
|
||||
NSUInteger count, len;
|
||||
struct SizedXid *changes;
|
||||
struct SPropValue property;
|
||||
struct SRow aRow;
|
||||
struct XID *currentChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t nChanges;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
|
||||
|
||||
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
if (!currentChangeList)
|
||||
{
|
||||
/* Create a new PredecessorChangeList */
|
||||
len = [changeKey length];
|
||||
newChangeList = [NSMutableData dataWithCapacity: len + 1];
|
||||
[newChangeList appendUInt8: len];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Update current predecessor change list with new change key */
|
||||
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
|
||||
with: &nChanges];
|
||||
|
||||
updated = NO;
|
||||
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
|
||||
for (count = 0; count < nChanges && !updated; count++)
|
||||
{
|
||||
if (GUID_equal(&changes[count].XID.NameSpaceGuid, ¤tChangeKey->NameSpaceGuid))
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
|
||||
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
|
||||
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
|
||||
{
|
||||
if ([globCnt length] != [oldGlobCnt length])
|
||||
{
|
||||
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
|
||||
abort();
|
||||
}
|
||||
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
|
||||
updated = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serialise it */
|
||||
changeKeys = [NSMutableArray array];
|
||||
|
||||
if (!updated)
|
||||
[changeKeys addObject: changeKey];
|
||||
|
||||
for (count = 0; count < nChanges; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithXID: &changes[count].XID];
|
||||
[changeKeys addObject: changeKey];
|
||||
}
|
||||
|
||||
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
|
||||
|
||||
newChangeList = [NSMutableData data];
|
||||
len = [changeKeys count];
|
||||
for (count = 0; count < len; count++)
|
||||
{
|
||||
changeKey = [changeKeys objectAtIndex: count];
|
||||
[newChangeList appendUInt8: [changeKey length]];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
}
|
||||
|
||||
if ([newChangeList length] > 0)
|
||||
{
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [self addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
//
|
||||
// FIXME: how this can happen?
|
||||
//
|
||||
@@ -166,6 +286,9 @@
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
||||
forKey: @"version"];
|
||||
|
||||
/* Update PredecessorChangeList accordingly */
|
||||
[self _updatePredecessorChangeList];
|
||||
|
||||
[self logWithFormat: @"%d props in dict", [properties count]];
|
||||
|
||||
[sogoObject save];
|
||||
@@ -209,4 +332,36 @@
|
||||
return [sogoObject lastModified];
|
||||
}
|
||||
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
/* Modify PidTagMessageFlags from SetMessageReadFlag and
|
||||
SyncImportReadStateChanges ROPs */
|
||||
NSNumber *flags;
|
||||
uint32_t newFlag;
|
||||
|
||||
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
if (flags)
|
||||
{
|
||||
newFlag = [flags unsignedLongValue];
|
||||
if (flag & SUPPRESS_RECEIPT)
|
||||
newFlag |= MSGFLAG_READ;
|
||||
if (flag & CLEAR_RN_PENDING)
|
||||
newFlag &= ~MSGFLAG_RN_PENDING;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag &= ~MSGFLAG_READ;
|
||||
if (flag & CLEAR_NRN_PENDING)
|
||||
newFlag &= ~MSGFLAG_NRN_PENDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
newFlag = MSGFLAG_READ;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag = 0x0;
|
||||
}
|
||||
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
|
||||
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -121,6 +121,7 @@
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) want_copy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
|
||||
|
||||
@@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMID: (uint64_t) targetMid
|
||||
andChangeKey: (struct Binary_r *) targetChangeKey
|
||||
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -669,15 +670,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
|
||||
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
|
||||
|
||||
if (targetChangeKey)
|
||||
if (targetPredecessorChangeList)
|
||||
{
|
||||
property.ulPropTag = PidTagChangeKey;
|
||||
property.value.bin = *targetChangeKey;
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *targetPredecessorChangeList;
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [destMsg addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
goto end;
|
||||
{
|
||||
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
[destMsg save: memCtx];
|
||||
if (!wantCopy)
|
||||
@@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
NSString *oldMessageURL;
|
||||
MAPIStoreMapping *mapping;
|
||||
SOGoUser *ownerUser;
|
||||
struct Binary_r *targetChangeKey;
|
||||
struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
|
||||
//TALLOC_CTX *memCtx;
|
||||
|
||||
//memCtx = talloc_zero (NULL, TALLOC_CTX);
|
||||
@@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
if (oldMessageURL)
|
||||
{
|
||||
[oldMessageURLs addObject: oldMessageURL];
|
||||
if (targetChangeKeys)
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
if (targetChangeKeys && targetPredecessorChangeList)
|
||||
{
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
targetPredecessorChangeList = targetPredecessorChangeLists[count];
|
||||
}
|
||||
else
|
||||
targetChangeKey = NULL;
|
||||
{
|
||||
targetChangeKey = NULL;
|
||||
targetPredecessorChangeList = NULL;
|
||||
}
|
||||
rc = [self _moveCopyMessageWithMID: srcMids[count]
|
||||
fromFolder: sourceFolder
|
||||
withMID: targetMids[count]
|
||||
andChangeKey: targetChangeKey
|
||||
andPredecessorChangeList: targetPredecessorChangeList
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
|
||||
@@ -41,8 +41,10 @@
|
||||
|
||||
/* synchronisation */
|
||||
- (BOOL) synchroniseCache;
|
||||
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey;
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl;
|
||||
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
|
||||
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
+194
-18
@@ -75,6 +75,7 @@ static Class NSNumberK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
@@ -261,7 +262,6 @@ static Class NSNumberK;
|
||||
*/
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
inChangeListOnly: (BOOL) inChangeListOnly
|
||||
{
|
||||
struct XID *xid;
|
||||
NSString *guid;
|
||||
@@ -270,19 +270,15 @@ static Class NSNumberK;
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
if (!inChangeListOnly)
|
||||
{
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
}
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
|
||||
/* 2. append/update predecessor change list */
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
@@ -296,6 +292,77 @@ static Class NSNumberK;
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
withOldChangeKey: (NSData *) oldChangeKey
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *changeKeyDict;
|
||||
NSString *guid;
|
||||
NSMutableDictionary *changeList;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid, *givenChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t i, length;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (predecessorChangeList)
|
||||
{
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldChangeKey)
|
||||
{
|
||||
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
|
||||
if (givenChangeKey) {
|
||||
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
|
||||
|
||||
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
|
||||
if (!changeKeyDict ||
|
||||
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
|
||||
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
|
||||
{
|
||||
/* The given change key is greater than current one stored in
|
||||
metadata or it does not exist */
|
||||
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil]
|
||||
forKey: @"ChangeKey"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
- (EOQualifier *) componentQualifier
|
||||
{
|
||||
if (!componentQualifier)
|
||||
@@ -465,8 +532,7 @@ static Class NSNumberK;
|
||||
// A GLOBCNT structure is a 6-byte global namespace counter,
|
||||
// we strip the first 2 bytes. The first two bytes is the ReplicaId
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: NO];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
}
|
||||
|
||||
now = [NSCalendarDate date];
|
||||
@@ -482,13 +548,123 @@ 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 *) newChangeKey
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
|
||||
[self synchroniseCache];
|
||||
if (newChangeKey)
|
||||
if (oldChangeKey || pcl)
|
||||
{
|
||||
messages = [[versionsMessage properties] objectForKey: @"Messages"];
|
||||
messageEntry = [messages objectForKey: messageKey];
|
||||
@@ -496,8 +672,8 @@ static Class NSNumberK;
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"no version record found for message '%@'",
|
||||
messageKey];
|
||||
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: YES];
|
||||
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
|
||||
withOldChangeKey: oldChangeKey];
|
||||
[versionsMessage save];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,8 @@
|
||||
{
|
||||
uint64_t version = ULLONG_MAX;
|
||||
NSString *changeNumber;
|
||||
|
||||
BOOL synced;
|
||||
|
||||
if (!isNew)
|
||||
{
|
||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||
@@ -189,16 +190,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;
|
||||
@@ -209,13 +222,16 @@
|
||||
|
||||
- (void) updateVersions
|
||||
{
|
||||
NSData *newChangeKey;
|
||||
/* Update ChangeKey and PredecessorChangeList on message's save */
|
||||
NSData *newChangeKey, *predecessorChangeList;
|
||||
|
||||
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
|
||||
|
||||
[(MAPIStoreGCSFolder *) container
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey];
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey
|
||||
andPredecessorChangeList: predecessorChangeList];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -30,6 +30,7 @@
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "MAPIStoreMailMessage.h"
|
||||
@@ -108,7 +109,7 @@
|
||||
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
|
||||
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
|
||||
0x68};
|
||||
|
||||
|
||||
*data = [[NSData dataWithBytes: recordBytes length: 16]
|
||||
asBinaryInMemCtx: memCtx];
|
||||
|
||||
@@ -117,19 +118,7 @@
|
||||
|
||||
- (NSString *) _fileName
|
||||
{
|
||||
NSString *fileName;
|
||||
NSDictionary *parameters;
|
||||
|
||||
fileName = [[bodyInfo objectForKey: @"parameterList"]
|
||||
objectForKey: @"name"];
|
||||
if (!fileName)
|
||||
{
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
fileName = [parameters objectForKey: @"filename"];
|
||||
}
|
||||
|
||||
return fileName;
|
||||
return [bodyInfo filename];
|
||||
}
|
||||
|
||||
- (int) getPidTagAttachLongFilename: (void **) data
|
||||
@@ -178,7 +167,7 @@
|
||||
|
||||
- (int) getPidTagAttachContentId: (void **) data
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
{
|
||||
*data = [[bodyInfo objectForKey: @"bodyId"]
|
||||
asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
|
||||
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
|
||||
#include <gen_ndr/exchange.h>
|
||||
|
||||
#undef DEBUG
|
||||
#include <util/attr.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
@@ -109,6 +111,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL) ensureFolderExists
|
||||
@@ -516,6 +519,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return [modseq1 compare: modseq2];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeListWith: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid;
|
||||
uint32_t i, length;
|
||||
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: NULL with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
talloc_free (sizedXIDList);
|
||||
}
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
@@ -526,8 +567,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
/* 1. set change key association */
|
||||
@@ -553,8 +594,7 @@ _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;
|
||||
@@ -593,7 +633,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
}
|
||||
|
||||
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
|
||||
initialLastModseq = lastModseq;
|
||||
if (lastModseq)
|
||||
{
|
||||
lastModseqNbr = [lastModseq unsignedLongLongValue];
|
||||
@@ -677,40 +716,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)
|
||||
{
|
||||
@@ -924,8 +960,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return changeNumber;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
NSString *messageUid;
|
||||
@@ -936,7 +971,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
messageEntry = [messages objectForKey: messageUid];
|
||||
if (!messageEntry)
|
||||
{
|
||||
[self warnWithFormat: @"attempting to synchronise to set the change key for "
|
||||
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
|
||||
@"this message %@", messageKey];
|
||||
synced = [self synchroniseCacheForUID: messageUid];
|
||||
if (synced)
|
||||
@@ -947,11 +982,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
|
||||
return messageEntry;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
[self _setChangeKey: changeKey
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
/* Update predecessor change list property given the change key. It
|
||||
returns if the change key has been added to the list or not */
|
||||
BOOL added = NO;
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *messageEntry;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct XID *xid;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
messageEntry = [self _messageEntryFromMessageKey: messageKey];
|
||||
if (messageEntry)
|
||||
{
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (changeList)
|
||||
{
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
{
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
[versionsMessage save];
|
||||
added = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"Missing predecessor change list to update"];
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
NSDictionary *messages, *changeKeyDict;
|
||||
@@ -1217,6 +1298,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
|
||||
@@ -1231,12 +1313,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
NSDictionary *result;
|
||||
NSUInteger count;
|
||||
NSArray *a;
|
||||
NSData *changeKey;
|
||||
NSData *changeList;
|
||||
|
||||
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
|
||||
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
|
||||
fromFolder: sourceFolder withMIDs: targetMids
|
||||
andChangeKeys: targetChangeKeys
|
||||
andPredecessorChangeLists: targetPredecessorChangeLists
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
|
||||
@@ -1325,11 +1408,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
[self synchroniseCache];
|
||||
for (count = 0; count < midCount; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
|
||||
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
|
||||
messageKey = [NSString stringWithFormat: @"%@.eml",
|
||||
[destUIDs objectAtIndex: count]];
|
||||
[self setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
[self _updatePredecessorChangeListWith: changeList
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#import <Mailer/NSData+Mail.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "Codepages.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
@@ -196,7 +197,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
count1 = [keys indexOfObject: data1];
|
||||
data2 = [entry2 objectForKey: @"mimeType"];
|
||||
count2 = [keys indexOfObject: data2];
|
||||
|
||||
|
||||
if (count1 == count2)
|
||||
{
|
||||
data1 = [entry1 objectForKey: @"key"];
|
||||
@@ -529,7 +530,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
else
|
||||
stringValue = @"";
|
||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -624,7 +625,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v = 0;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
|
||||
@@ -636,7 +637,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([[self attachmentKeys]
|
||||
count] > 0)
|
||||
v |= MSGFLAG_HASATTACH;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -656,7 +657,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
v = 2;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -668,15 +669,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
|
||||
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
if ([flags containsObject: @"flagged"])
|
||||
v = 6;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -755,7 +756,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
||||
{
|
||||
cn = [ngAddress displayName];
|
||||
|
||||
|
||||
// If we don't have a displayName, we use the email address instead. This
|
||||
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
|
||||
// nor in the recipient field if we reply to the email.
|
||||
@@ -809,7 +810,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
entryId = MAPIStoreExternalEntryId (cn, email);
|
||||
|
||||
*data = [entryId asBinaryInMemCtx: memCtx];
|
||||
|
||||
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
@@ -966,15 +967,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
{
|
||||
uint32_t v;
|
||||
NSString *s;
|
||||
|
||||
|
||||
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
|
||||
v = 0x1;
|
||||
|
||||
|
||||
if ([s hasPrefix: @"1"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"2"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"4"]) v = 0x0;
|
||||
else if ([s hasPrefix: @"5"]) v = 0x0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1163,14 +1164,14 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
|
||||
if ([headerMimeType isEqualToString: @"text/plain"])
|
||||
format = EDITOR_FORMAT_PLAINTEXT;
|
||||
else if ([headerMimeType isEqualToString: @"text/html"])
|
||||
format = EDITOR_FORMAT_HTML;
|
||||
else
|
||||
format = 0; /* EDITOR_FORMAT_DONTKNOW */
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, format);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1517,7 +1518,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
p = 0;
|
||||
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
|
||||
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
|
||||
|
||||
|
||||
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
|
||||
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
|
||||
p++;
|
||||
@@ -1525,7 +1526,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0);
|
||||
p++;
|
||||
|
||||
|
||||
// PR_7BIT_DISPLAY_NAME_UNICODE
|
||||
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
@@ -1533,7 +1534,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_SMTP_ADDRESS_UNICODE
|
||||
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
|
||||
|
||||
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
|
||||
p++;
|
||||
@@ -1566,12 +1567,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
withPrefix: (NSString *) keyPrefix
|
||||
{
|
||||
NSArray *parts;
|
||||
NSDictionary *parameters;
|
||||
NSUInteger count, max;
|
||||
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
if ([[parameters objectForKey: @"filename"] length] > 0)
|
||||
if ([[bodyInfo filename] length] > 0)
|
||||
{
|
||||
if ([keyPrefix length] == 0)
|
||||
keyPrefix = @"0";
|
||||
@@ -1645,15 +1643,13 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return attachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
/* TODO: notifications should probably be emitted from here */
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
|
||||
else
|
||||
[sogoObject addFlags: imapFlag];
|
||||
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
@@ -1694,9 +1690,12 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
- (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)
|
||||
@@ -1706,8 +1705,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];
|
||||
|
||||
@@ -161,15 +161,30 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
|
||||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
|
||||
if (modseq)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
{
|
||||
if (res->relop == RELOP_GT)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
|
||||
}
|
||||
else
|
||||
modseq = [NSNumber numberWithUnsignedLongLong: 0];
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
|
||||
if (res->relop == RELOP_GT || res->relop == RELOP_GE)
|
||||
{
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ignore other operations as IMAP only support MODSEQ >= X */
|
||||
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
|
||||
[self operatorFromRestrictionOperator: res->relop]];
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
#import <NGExtensions/NGHashMap.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSObject+Values.h>
|
||||
#import <NGExtensions/NSString+Encoding.h>
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
@@ -166,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
|
||||
@@ -232,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];
|
||||
@@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
version = [properties objectForKey: @"version"];
|
||||
|
||||
return (version
|
||||
? exchange_globcnt ([version unsignedLongLongValue])
|
||||
? [version unsignedLongLongValue]
|
||||
: ULLONG_MAX);
|
||||
}
|
||||
|
||||
@@ -1110,7 +1111,8 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSString *folderName, *flag, *newIdString, *messageKey;
|
||||
BOOL updatedMetadata;
|
||||
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
|
||||
NSData *changeKey, *messageData;
|
||||
NGImap4Connection *connection;
|
||||
NGImap4Client *client;
|
||||
@@ -1146,21 +1148,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
[sogoObject setNameInContainer: messageKey];
|
||||
[mapping registerURL: [self url] withID: mid];
|
||||
|
||||
/* synchronise the cache and update the change key with the one provided
|
||||
by the client. Before doing this, lets issue a unselect/select combo
|
||||
because of timing issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if we do
|
||||
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
|
||||
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
|
||||
/* synchronise the cache and update the predecessor change list
|
||||
with the change key provided by the client. Before doing
|
||||
this, lets issue a unselect/select combo because of timing
|
||||
issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if
|
||||
we do a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)"
|
||||
command (where XYZ is the HIGHESTMODSEQ+1) immediately after
|
||||
IMAP APPEND */
|
||||
[client unselect];
|
||||
[client select: folderName];
|
||||
|
||||
[(MAPIStoreMailFolder *) container synchroniseCache];
|
||||
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
if (changeKey)
|
||||
[(MAPIStoreMailFolder *) container
|
||||
setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
{
|
||||
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
if (!updatedMetadata)
|
||||
[self warnWithFormat: @"Predecessor change list not updated with client data"];
|
||||
}
|
||||
|
||||
/* Update version property (PR_CHANGE_KEY indeed) as it is
|
||||
requested once it is saved */
|
||||
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
|
||||
if (changeNumber)
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
|
||||
forKey: @"version"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
withAID: (uint32_t) aid;
|
||||
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
|
||||
andRowCount: (uint32_t *) countPtr;
|
||||
- (int) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
|
||||
|
||||
- (NSArray *) activeContainerMessageTables;
|
||||
|
||||
@@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
|
||||
}
|
||||
|
||||
[self save: memCtx];
|
||||
/* We make sure that any change-related properties are removes from the
|
||||
/* We make sure that any change-related properties are removed from the
|
||||
properties dictionary, to make sure that related methods will be
|
||||
invoked the next time they are requested. */
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
|
||||
if ([container isKindOfClass: MAPIStoreFolderK])
|
||||
{
|
||||
@@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
|
||||
return [self getNo: data inMemCtx: memCtx];;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
// [self subclassResponsibility: _cmd];
|
||||
|
||||
|
||||
@@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
uint32_t mid_count,
|
||||
uint64_t *src_mids, uint64_t *t_mids,
|
||||
struct Binary_r **target_change_keys,
|
||||
struct Binary_r **target_predecessor_change_lists,
|
||||
uint8_t want_copy)
|
||||
{
|
||||
MAPIStoreFolder *sourceFolder, *targetFolder;
|
||||
@@ -698,6 +699,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
fromFolder: sourceFolder
|
||||
withMIDs: t_mids
|
||||
andChangeKeys: target_change_keys
|
||||
andPredecessorChangeLists: target_predecessor_change_lists
|
||||
wantCopy: want_copy
|
||||
inMemCtx: mem_ctx];
|
||||
TRYCATCH_END(pool)
|
||||
@@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
|
||||
struct MAPIStoreTallocWrapper *wrapper;
|
||||
NSAutoreleasePool *pool;
|
||||
MAPIStoreMessage *message;
|
||||
int rc;
|
||||
enum mapistore_error rc;
|
||||
|
||||
if (message_object)
|
||||
{
|
||||
|
||||
@@ -531,7 +531,7 @@
|
||||
}
|
||||
[vToDo setTimeStampAsDate: now];
|
||||
|
||||
[sogoObject saveComponent: vCalendar];
|
||||
[sogoObject saveCalendar: vCalendar];
|
||||
|
||||
[self updateVersions];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
|
||||
+ (id) dataWithXID: (const struct XID *) xid;
|
||||
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length;
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
#undef DEBUG
|
||||
#include <stdbool.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
#include <talloc.h>
|
||||
#include <util/time.h>
|
||||
#include <gen_ndr/exchange.h>
|
||||
@@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
NSMutableData *xidData;
|
||||
struct FlatUID_r flatUID;
|
||||
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->GUID);
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->NameSpaceGuid);
|
||||
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->Size];
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->LocalId.length];
|
||||
[xidData appendBytes: flatUID.ab length: 16];
|
||||
[xidData appendBytes: xid->Data length: xid->Size];
|
||||
[xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
|
||||
return xidData;
|
||||
}
|
||||
@@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
{
|
||||
xid = talloc_zero (memCtx, struct XID);
|
||||
|
||||
[self _extractGUID: &xid->GUID];
|
||||
[self _extractGUID: &xid->NameSpaceGuid];
|
||||
|
||||
xid->Size = max - 16;
|
||||
xid->LocalId.length = max - 16;
|
||||
|
||||
bytes = (uint8_t *) [self bytes];
|
||||
xid->Data = talloc_memdup (xid, (bytes+16), xid->Size);
|
||||
xid->LocalId.data = talloc_memdup (xid, (bytes+16), xid->LocalId.length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
return xid;
|
||||
}
|
||||
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length
|
||||
{
|
||||
struct Binary_r bin;
|
||||
struct SizedXid *sizedXIDArray;
|
||||
|
||||
bin.cb = [self length];
|
||||
bin.lpb = (uint8_t *)[self bytes];
|
||||
|
||||
sizedXIDArray = get_SizedXidArray(memCtx, &bin, length);
|
||||
if (!sizedXIDArray)
|
||||
{
|
||||
NSLog (@"Impossible to parse SizedXID array");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sizedXIDArray;
|
||||
}
|
||||
|
||||
- (NSComparisonResult) compare: (NSData *) otherGlobCnt
|
||||
{
|
||||
uint64_t globCnt = 0, oGlobCnt = 0;
|
||||
|
||||
if ([self length] > 0)
|
||||
globCnt = *(uint64_t *) [self bytes];
|
||||
|
||||
if ([otherGlobCnt length] > 0)
|
||||
oGlobCnt = *(uint64_t *) [otherGlobCnt bytes];
|
||||
|
||||
return MAPICNCompare (globCnt, oGlobCnt, NULL);
|
||||
}
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
{
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
[](http://www.sogo.nu/)
|
||||
|
||||
Share your calendars, address books and mails in your community with a completely free and open source solution.
|
||||
|
||||
Let your Mozilla Thunderbird/Lightning, Microsoft Outlook, Apple iCal/iPhone, Android and BlackBerry users collaborate using a modern platform.
|
||||
|
||||
## What is SOGo?
|
||||
|
||||
SOGo is [fully supported](http://www.sogo.nu/support/commercial_support.html) and [trusted](http://www.sogo.nu/tour/testimonials.html) groupware server with a focus on scalability and open standards. SOGo is released under the GNU GPL/LGPL v2 and above.
|
||||
|
||||
SOGo provides a rich AJAX-based Web interface and supports multiple native clients through the use of standard protocols such as CalDAV, CardDAV and GroupDAV.
|
||||
|
||||
SOGo is the missing component of your infrastructure; it sits in the middle of your servers to offer your users an uniform and complete interface to access their information. It has been deployed in production environments where thousands of users are involved.
|
||||
|
||||
[](http://www.sogo.nu/)
|
||||
|
||||
[Learn more...](http://www.sogo.nu/about/overview.html)
|
||||
|
||||
## Microsoft Outlook Compatible
|
||||
|
||||
SOGo offers native Microsoft Outlook compatibility using the [OpenChange](http://www.openchange.org/) backend developed by [Inverse](http://www.inverse.ca/). This means Microsoft Outlook 2003, 2007, 2010 and 2013 can talk directly to SOGo – just like if it was a Microsoft Exchange server. **No plugins are required in Outlook to make this work**.
|
||||
|
||||
The following screencast shows Outlook 2010 connected to SOGo as if it was an Exchange server, along the Web interface of SOGo.
|
||||
|
||||
[](http://www.youtube.com/watch?v=1hcBSB4Kxww)
|
||||
|
||||
[](https://github.com/inverse-inc/sogo/) [](https://www.ohloh.net/p/sogo) [](http://twitter.com/scalableogo) [](http://sourceforge.net/projects/sogo-zeg) [](https://freecode.com/projects/sogo-inverse) [](http://www.linkedin.com/groups/SOGo-Groupware-4164805)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* iCalByDayMask.h - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2010-2015 Inverse inc.
|
||||
* Copyright (C) 2010 Wolfgang Sourdeau
|
||||
*
|
||||
* 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
|
||||
@@ -53,8 +55,6 @@ typedef iCalWeekOccurrence iCalWeekOccurrences[7];
|
||||
- (id) initWithDays: (iCalWeekOccurrences) theDays;
|
||||
+ (id) byDayMaskWithRuleString: (NSString *) byDayRule;
|
||||
- (id) initWithRuleString: (NSString *) byDayRule;
|
||||
+ (id) byDayMaskWithDaysAndOccurrences: (NSArray *) values;
|
||||
- (id) initWithDaysAndOccurrences: (NSArray *) values;
|
||||
|
||||
- (BOOL) occursOnDay: (iCalWeekDay) weekDay;
|
||||
- (BOOL) occursOnDay: (iCalWeekDay) weekDay
|
||||
@@ -71,7 +71,6 @@ typedef iCalWeekOccurrence iCalWeekOccurrences[7];
|
||||
|
||||
- (NSString *) asRuleString;
|
||||
- (NSString *) asRuleStringWithIntegers;
|
||||
- (NSArray *) asRuleArray;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+20
-159
@@ -1,6 +1,8 @@
|
||||
/* iCalByDayMask.m - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2010-2015 Inverse inc.
|
||||
* Copyright (C) 2010 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
@@ -19,9 +21,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import "iCalByDayMask.h"
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
+ (id) byDayMaskWithDays: (iCalWeekOccurrences) theDays
|
||||
{
|
||||
id o;
|
||||
|
||||
|
||||
o = [[self alloc] initWithDays: theDays];
|
||||
AUTORELEASE(o);
|
||||
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
{
|
||||
id o;
|
||||
iCalWeekOccurrences d;
|
||||
|
||||
|
||||
d[iCalWeekDaySunday] = 0;
|
||||
d[iCalWeekDayMonday] = iCalWeekOccurrenceAll;
|
||||
d[iCalWeekDayTuesday] = iCalWeekOccurrenceAll;
|
||||
@@ -72,7 +72,7 @@
|
||||
d[iCalWeekDaySaturday] = 0;
|
||||
o = [[self alloc] initWithDays: d];
|
||||
AUTORELEASE(o);
|
||||
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
if (self)
|
||||
{
|
||||
memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
|
||||
|
||||
|
||||
if ([byDayRule length] > 0)
|
||||
{
|
||||
values = [byDayRule componentsSeparatedByString: @","];
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
[value getCharacters: chars
|
||||
range: NSMakeRange(valueLength - 2, 2)];
|
||||
|
||||
|
||||
switch (chars[0])
|
||||
{
|
||||
case 'M': day = iCalWeekDayMonday;
|
||||
@@ -142,7 +142,7 @@
|
||||
day = iCalWeekDaySunday;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (day != iCalWeekDayUnknown)
|
||||
{
|
||||
c = [value characterAtIndex: 0];
|
||||
@@ -157,15 +157,15 @@
|
||||
digitStart = 1;
|
||||
c = [value characterAtIndex: 1];
|
||||
}
|
||||
|
||||
|
||||
i = digitStart;
|
||||
|
||||
|
||||
while (i < valueLength && isdigit(c))
|
||||
{
|
||||
i++;
|
||||
c = [value characterAtIndex: i];
|
||||
}
|
||||
|
||||
|
||||
if (i != digitStart)
|
||||
order = [[value substringWithRange: NSMakeRange(digitStart, (i - digitStart))] intValue];
|
||||
|
||||
@@ -186,123 +186,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id) byDayMaskWithDaysAndOccurrences: (NSArray *) values
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [[self alloc] initWithDaysAndOccurrences: values];
|
||||
AUTORELEASE(o);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
- (id) initWithDaysAndOccurrences: (NSArray *) values
|
||||
{
|
||||
unsigned int count, max;
|
||||
NSString *value;
|
||||
unichar c, chars[2];
|
||||
unsigned int valueLength, i, digitStart, order;
|
||||
iCalWeekDay day;
|
||||
id mask;
|
||||
BOOL reverse;
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
|
||||
|
||||
max = [values count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
mask = [values objectAtIndex: count];
|
||||
if (![mask isKindOfClass: [NSDictionary class]])
|
||||
continue;
|
||||
value = [[mask objectForKey: @"day"] uppercaseString];
|
||||
valueLength = [value length];
|
||||
if (valueLength > 1)
|
||||
{
|
||||
day = iCalWeekDayUnknown;
|
||||
reverse = NO;
|
||||
digitStart = 0;
|
||||
order = 0;
|
||||
|
||||
[value getCharacters: chars
|
||||
range: NSMakeRange(0, valueLength)];
|
||||
|
||||
switch (chars[0])
|
||||
{
|
||||
case 'M': day = iCalWeekDayMonday;
|
||||
break;
|
||||
case 'W': day = iCalWeekDayWednesday;
|
||||
break;
|
||||
case 'F': day = iCalWeekDayFriday;
|
||||
break;
|
||||
case 'T':
|
||||
if (chars[1] == 'U')
|
||||
day = iCalWeekDayTuesday;
|
||||
else if (chars[1] == 'H')
|
||||
day = iCalWeekDayThursday;
|
||||
break;
|
||||
case 'S':
|
||||
if (chars[1] == 'A')
|
||||
day = iCalWeekDaySaturday;
|
||||
else if (chars[1] == 'U')
|
||||
day = iCalWeekDaySunday;
|
||||
break;
|
||||
}
|
||||
|
||||
if (day != iCalWeekDayUnknown)
|
||||
{
|
||||
value = [mask objectForKey: @"occurrence"];
|
||||
valueLength = [value length];
|
||||
if (valueLength > 0)
|
||||
{
|
||||
c = [value characterAtIndex: 0];
|
||||
if (c == '-')
|
||||
{
|
||||
digitStart = 1;
|
||||
reverse = YES;
|
||||
}
|
||||
else if (c == '+')
|
||||
{
|
||||
digitStart = 1;
|
||||
}
|
||||
|
||||
i = digitStart;
|
||||
while (i < valueLength)
|
||||
{
|
||||
c = [value characterAtIndex: i];
|
||||
i++;
|
||||
if (!isdigit(c))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i != digitStart)
|
||||
order = [[value substringWithRange: NSMakeRange(digitStart, (i - digitStart))] intValue];
|
||||
}
|
||||
|
||||
if (order > 0 && order < 6)
|
||||
{
|
||||
order = pow (2, order - 1);
|
||||
if (reverse)
|
||||
order = order << 5;
|
||||
days[day] |= order;
|
||||
//NSLog(@"*** iCalByDayMask [%i] %@ : day = %i, order = %i, result = %i", count, byDayRule, day, order, days[day]);
|
||||
}
|
||||
else
|
||||
{
|
||||
days[day] = iCalWeekOccurrenceAll;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -330,7 +214,7 @@
|
||||
withWeekNumber: (int) week
|
||||
{
|
||||
unsigned int absWeek, order;
|
||||
|
||||
|
||||
absWeek = abs (week);
|
||||
order = 0;
|
||||
if (absWeek > 0 && absWeek < 6)
|
||||
@@ -339,7 +223,7 @@
|
||||
if (week < 0)
|
||||
order = order << 5;
|
||||
}
|
||||
|
||||
|
||||
return ((days[weekDay] & order) > 0);
|
||||
}
|
||||
|
||||
@@ -365,7 +249,7 @@
|
||||
if (days[i])
|
||||
day = i;
|
||||
}
|
||||
|
||||
|
||||
return day;
|
||||
}
|
||||
|
||||
@@ -376,10 +260,10 @@
|
||||
|
||||
occurrence = 0;
|
||||
day = [self firstDay];
|
||||
|
||||
|
||||
if (day > -1 && days[day] != iCalWeekOccurrenceAll)
|
||||
occurrence = [self _iCalWeekOccurrenceIntValue: days[day]];
|
||||
|
||||
|
||||
return occurrence;
|
||||
}
|
||||
|
||||
@@ -439,7 +323,7 @@
|
||||
[rules addObject: rule];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [rules componentsJoinedByString: @","];
|
||||
}
|
||||
|
||||
@@ -460,27 +344,4 @@
|
||||
return s;
|
||||
}
|
||||
|
||||
- (NSArray *) asRuleArray
|
||||
{
|
||||
NSMutableArray *rules;
|
||||
NSMutableDictionary *rule;
|
||||
int i;
|
||||
|
||||
rules = [NSMutableArray array];
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
if (days[i])
|
||||
{
|
||||
rule = [NSMutableDictionary dictionary];
|
||||
if (days[i] != iCalWeekOccurrenceAll)
|
||||
[rule setObject: [NSNumber numberWithInt: [self _iCalWeekOccurrenceIntValue: days[i]]]
|
||||
forKey: @"occurrence"];
|
||||
[rule setObject: iCalWeekDayString[i]
|
||||
forKey: @"day"];
|
||||
[rules addObject: rule];
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
@end /* iCalByDayMask */
|
||||
@@ -94,8 +94,8 @@ typedef enum
|
||||
- (void) increaseSequence;
|
||||
|
||||
/* url can either be set as NSString or NSURL */
|
||||
- (void) setAttach: (NSArray *) _value;
|
||||
- (NSArray *) attach;
|
||||
- (void) setAttach: (id) _value;
|
||||
- (NSURL *) attach;
|
||||
- (void) setUrl: (id) _value;
|
||||
- (NSURL *) url;
|
||||
|
||||
|
||||
@@ -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];
|
||||
@@ -400,49 +404,32 @@
|
||||
return [self childrenWithTag: @"valarm"];
|
||||
}
|
||||
|
||||
- (void) setAttach: (NSArray *) _value
|
||||
- (void) setAttach: (id) _value
|
||||
{
|
||||
NSString *aString;
|
||||
id anObject;
|
||||
int count, max;
|
||||
|
||||
max = [_value count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
anObject = [_value objectAtIndex: count];
|
||||
if ([anObject isKindOfClass: [NSURL class]])
|
||||
{
|
||||
aString = [anObject absoluteString];
|
||||
}
|
||||
else
|
||||
aString = anObject;
|
||||
[self addChild: [CardElement simpleElementWithTag: @"attach" value: aString]];
|
||||
}
|
||||
if ([_value isKindOfClass: [NSString class]])
|
||||
aString = _value;
|
||||
else if ([_value isKindOfClass: [NSURL class]])
|
||||
aString = [_value absoluteString];
|
||||
else
|
||||
aString = @"";
|
||||
|
||||
[[self uniqueChildWithTag: @"attach"] setSingleValue: aString forKey: @""];
|
||||
}
|
||||
|
||||
- (NSArray *) attach
|
||||
- (NSURL *) attach
|
||||
{
|
||||
NSArray *elements;
|
||||
NSMutableArray *attachUrls;
|
||||
NSString *stringAttach;
|
||||
NSURL *url;
|
||||
int count, max;
|
||||
|
||||
elements = [self childrenWithTag: @"attach"];
|
||||
max = [elements count];
|
||||
attachUrls = [NSMutableArray arrayWithCapacity: max];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
stringAttach = [[elements objectAtIndex: count] flattenedValuesForKey: @""];
|
||||
url = [NSURL URLWithString: stringAttach];
|
||||
stringAttach = [[self uniqueChildWithTag: @"attach"] flattenedValuesForKey: @""];
|
||||
url = [NSURL URLWithString: stringAttach];
|
||||
|
||||
if (!url && [stringAttach length] > 0)
|
||||
url = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", stringAttach]];
|
||||
|
||||
[attachUrls addObject: [url absoluteString]];
|
||||
}
|
||||
if (!url && [stringAttach length] > 0)
|
||||
url = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", stringAttach]];
|
||||
|
||||
return attachUrls;
|
||||
return url;
|
||||
}
|
||||
|
||||
- (void) setUrl: (id) _value
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,6 @@ typedef enum {
|
||||
|
||||
@interface iCalPerson : CardElement
|
||||
|
||||
+ (NSString *) descriptionForParticipationStatus: (iCalPersonPartStat) _status;
|
||||
|
||||
/* accessors */
|
||||
|
||||
- (void)setCn:(NSString *)_s;
|
||||
|
||||
+35
-46
@@ -25,48 +25,6 @@
|
||||
|
||||
@implementation iCalPerson
|
||||
|
||||
+ (NSString *) descriptionForParticipationStatus: (iCalPersonPartStat) _status
|
||||
{
|
||||
NSString *stat;
|
||||
|
||||
switch (_status) {
|
||||
case iCalPersonPartStatUndefined:
|
||||
stat = @"";
|
||||
break;
|
||||
case iCalPersonPartStatAccepted:
|
||||
stat = @"ACCEPTED";
|
||||
break;
|
||||
case iCalPersonPartStatDeclined:
|
||||
stat = @"DECLINED";
|
||||
break;
|
||||
case iCalPersonPartStatTentative:
|
||||
stat = @"TENTATIVE";
|
||||
break;
|
||||
case iCalPersonPartStatDelegated:
|
||||
stat = @"DELEGATED";
|
||||
break;
|
||||
case iCalPersonPartStatCompleted:
|
||||
stat = @"COMPLETED";
|
||||
break;
|
||||
case iCalPersonPartStatInProcess:
|
||||
stat = @"IN-PROCESS";
|
||||
break;
|
||||
case iCalPersonPartStatExperimental:
|
||||
case iCalPersonPartStatOther:
|
||||
// [NSException raise:NSInternalInconsistencyException
|
||||
// format:@"Attempt to set meaningless "
|
||||
// @"participationStatus (%d)!", _status];
|
||||
stat = nil; /* keep compiler happy */
|
||||
break;
|
||||
default:
|
||||
stat = @"NEEDS-ACTION";
|
||||
break;
|
||||
}
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
|
||||
/* accessors */
|
||||
|
||||
- (void) setCn: (NSString *) _s
|
||||
@@ -174,13 +132,44 @@
|
||||
{
|
||||
NSString *stat;
|
||||
|
||||
stat = [iCalPerson descriptionForParticipationStatus: _status];
|
||||
|
||||
switch (_status) {
|
||||
case iCalPersonPartStatUndefined:
|
||||
stat = @"";
|
||||
break;
|
||||
case iCalPersonPartStatAccepted:
|
||||
stat = @"ACCEPTED";
|
||||
break;
|
||||
case iCalPersonPartStatDeclined:
|
||||
stat = @"DECLINED";
|
||||
break;
|
||||
case iCalPersonPartStatTentative:
|
||||
stat = @"TENTATIVE";
|
||||
break;
|
||||
case iCalPersonPartStatDelegated:
|
||||
stat = @"DELEGATED";
|
||||
break;
|
||||
case iCalPersonPartStatCompleted:
|
||||
stat = @"COMPLETED";
|
||||
break;
|
||||
case iCalPersonPartStatInProcess:
|
||||
stat = @"IN-PROCESS";
|
||||
break;
|
||||
case iCalPersonPartStatExperimental:
|
||||
case iCalPersonPartStatOther:
|
||||
// [NSException raise:NSInternalInconsistencyException
|
||||
// format:@"Attempt to set meaningless "
|
||||
// @"participationStatus (%d)!", _status];
|
||||
stat = nil; /* keep compiler happy */
|
||||
break;
|
||||
default:
|
||||
stat = @"NEEDS-ACTION";
|
||||
break;
|
||||
}
|
||||
if (stat)
|
||||
[self setPartStat:stat];
|
||||
}
|
||||
|
||||
- (iCalPersonPartStat) participationStatus {
|
||||
- (iCalPersonPartStat)participationStatus {
|
||||
NSString *stat;
|
||||
|
||||
stat = [[self partStat] uppercaseString];
|
||||
@@ -267,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;
|
||||
|
||||
@@ -75,7 +75,6 @@ extern NSString *iCalWeekDayString[];
|
||||
- (void) setFrequency: (iCalRecurrenceFrequency) _frequency;
|
||||
- (iCalRecurrenceFrequency) frequency;
|
||||
- (iCalRecurrenceFrequency) valueForFrequency: (NSString *) value;
|
||||
- (NSString *) frequencyForValue: (iCalRecurrenceFrequency) freq;
|
||||
|
||||
- (void) setRepeatInterval: (int) _repeatInterval;
|
||||
- (int) repeatInterval;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalTimeZone.m - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
* Copyright (C) 2006-2014 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
@@ -147,8 +147,7 @@ static NSArray *knownTimeZones;
|
||||
if (ext.location != NSNotFound)
|
||||
{
|
||||
zone = [zone substringToIndex: ext.location];
|
||||
if (![timeZoneNames containsObject: zone])
|
||||
[timeZoneNames addObject: zone];
|
||||
[timeZoneNames addObject: zone];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,11 @@
|
||||
minute: [tzStart minuteOfHour] second: 0
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
|
||||
|
||||
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1)
|
||||
day: 0 hour: 0 minute: 0
|
||||
tmpDate = [tmpDate addYear: 0
|
||||
month: ((pos > 0) ? 0 : 1)
|
||||
day: 0
|
||||
hour: 0
|
||||
minute: 0
|
||||
second: 0];
|
||||
|
||||
/* If the day of the time change is "-XSU", we need to determine whether the
|
||||
@@ -197,12 +200,41 @@
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
|
||||
The time changes occure on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
The time changes occur on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
|
||||
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
|
||||
effectively shift the time change by a whole week.
|
||||
|
||||
In Europe/Berlin, we have a different use-case for November. In 2015, November 1st is a Sunday.
|
||||
The time change in November must occur on October 25th but since tmpDate will be November 1st,
|
||||
so a Sunday, dateDayOfWeek will be 0 and dayOfWeek will also be 0 we would decrement tmpDate by 14 days,
|
||||
which is incorrect because it would shift the timezone change one week earlier. We take care about this
|
||||
one with check if pos is greater or equal than 0 and if so, we don't decrement it.
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Inverse inc.//NONSGML Olson 2014g//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR
|
||||
*/
|
||||
if (dayOfWeek == dateDayOfWeek)
|
||||
if (dayOfWeek == dateDayOfWeek && pos >= 0)
|
||||
pos--;
|
||||
|
||||
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
- (NSString *) percentComplete;
|
||||
|
||||
- (void) setDue: (NSCalendarDate *) _date;
|
||||
- (void) setAllDayDue: (NSCalendarDate *) _date;
|
||||
- (NSCalendarDate *) due;
|
||||
|
||||
- (void) setCompleted: (NSCalendarDate *) _date;
|
||||
|
||||
@@ -65,12 +65,6 @@
|
||||
setDateTime: newDueDate];
|
||||
}
|
||||
|
||||
- (void) setAllDayDue: (NSCalendarDate *) newDueDate
|
||||
{
|
||||
[(iCalDateTime *) [self uniqueChildWithTag: @"due"]
|
||||
setDate: newDueDate];
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) due
|
||||
{
|
||||
return [(iCalDateTime *) [self uniqueChildWithTag: @"due"]
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
|
||||
- (NSCalendarDate *) nextAlarmDate;
|
||||
|
||||
- (NSDictionary *) asDictionary;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __NGCards_iCalTrigger_H__ */
|
||||
@@ -20,9 +20,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import "iCalEvent.h"
|
||||
#import "iCalTrigger.h"
|
||||
@@ -54,78 +52,6 @@
|
||||
return [self value: 0 ofAttribute: @"related"];
|
||||
}
|
||||
|
||||
- (NSDictionary *) asDictionary
|
||||
{
|
||||
NSDictionary *data;
|
||||
NSString *duration, *relation, *reference, *quantity, *unit;
|
||||
NSUInteger i;
|
||||
unichar c;
|
||||
|
||||
data = nil;
|
||||
if (![[self valueType] length] ||
|
||||
[[self valueType] caseInsensitiveCompare: @"DURATION"] == NSOrderedSame)
|
||||
{
|
||||
relation = [[self relationType] uppercaseString];
|
||||
duration = [self flattenedValuesForKey: @""];
|
||||
i = 0;
|
||||
c = [duration characterAtIndex: i];
|
||||
if (c == '-')
|
||||
{
|
||||
reference = @"BEFORE";
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
reference = @"AFTER";
|
||||
}
|
||||
c = [duration characterAtIndex: i];
|
||||
if (c == 'P')
|
||||
{
|
||||
quantity = @"";
|
||||
unit = @"";
|
||||
// Parse duration -- ignore first character (P)
|
||||
for (i++; i < [duration length]; i++)
|
||||
{
|
||||
c = [duration characterAtIndex: i];
|
||||
if (c == 't' || c == 'T')
|
||||
// time -- ignore character
|
||||
continue;
|
||||
else if (isdigit (c))
|
||||
quantity = [quantity stringByAppendingFormat: @"%c", c];
|
||||
else
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'D': /* day */
|
||||
unit = @"DAYS";
|
||||
break;
|
||||
case 'H': /* hour */
|
||||
unit = @"HOURS";
|
||||
break;
|
||||
case 'M': /* min */
|
||||
unit = @"MINUTES";
|
||||
break;
|
||||
case 'W': /* week */
|
||||
unit = @"WEEKS";
|
||||
break;
|
||||
default:
|
||||
NSLog(@"Cannot process duration unit: '%c'", c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
relation, @"relation",
|
||||
reference, @"reference",
|
||||
[NSNumber numberWithInt: [quantity intValue]], @"quantity",
|
||||
unit, @"unit",
|
||||
nil];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) nextAlarmDate
|
||||
{
|
||||
NSCalendarDate *relationDate, *nextAlarmDate;
|
||||
|
||||
@@ -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,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}。";
|
||||
@@ -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}.";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Vertrouwelijke afspraak)";
|
||||
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 Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian 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}\".";
|
||||
@@ -6,24 +6,19 @@ vevent_class2 = "(Evento confidenziale)";
|
||||
vtodo_class0 = "(Attività pubblica)";
|
||||
vtodo_class1 = "(Attività privata)";
|
||||
vtodo_class2 = "(Attività confidenziale)";
|
||||
|
||||
/* Receipts */
|
||||
"Title:" = "Titolo: ";
|
||||
"Start:" = "Inizio: ";
|
||||
"End:" = "Fine: ";
|
||||
|
||||
"Receipt: users invited to a meeting" = "Notifica: utenti invitati al meeting";
|
||||
"You have invited the following attendees(s):" = "Hai invitato i seguenti partecipanti:";
|
||||
"... to attend the following event:" = "... a partecipare al seguente evento:";
|
||||
|
||||
"Receipt: invitation updated" = "Notifica: invito aggiornato";
|
||||
"The following attendees(s):" = "I seguenti partecipanti:";
|
||||
"... have been notified of the changes to the following event:" = "... hanno ricevuto una notifica di cambiamento relativa al seguente evento:";
|
||||
|
||||
"Receipt: attendees removed from an event" = "Notifica: partecipanti rimossi dall'evento";
|
||||
"You have removed the following attendees(s):" = "Hai rimosso i seguenti partecipanti:";
|
||||
"... from the following event:" = "... dal seguente evento:";
|
||||
|
||||
/* IMIP messages */
|
||||
"startDate_label" = "Inizio";
|
||||
"endDate_label" = "Fine";
|
||||
@@ -31,17 +26,14 @@ vtodo_class2 = "(Attività confidenziale)";
|
||||
"location_label" = "Luogo";
|
||||
"summary_label" = "Summario:";
|
||||
"comment_label" = "Commento:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Invito Evento: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(inviato da %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}ti ha invitato al %{Summary}.\n\nInizio: %{StartDate} alle %{StartTime}\nFine: %{EndDate} alle %{EndTime}\nDescrizione: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Evento cancellato: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}ha cancellato questo evento: %{Summary}.\n\nInizio: %{StartDate} alle %{StartTime}\nFine: %{EndDate} alle %{EndTime}\nDescrizione: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
|
||||
= "L'appuntamento \"%{Summary}\" per la data %{OldStartDate} all'ora %{OldStartTime} è cambiato";
|
||||
@@ -49,7 +41,6 @@ vtodo_class2 = "(Attività confidenziale)";
|
||||
= "I seguenti dati sono cambiati nell'appuntamento \"%{Summary}\" :";
|
||||
"Please accept or decline those changes."
|
||||
= "Prego accettare o rifiutare questi cambiamenti";
|
||||
|
||||
/* Reply */
|
||||
"%{Attendee} %{SentByText}has accepted your event invitation."
|
||||
= "%{Attendee} %{SentByText}ha accettato il tuo invito al evento.";
|
||||
@@ -59,6 +50,5 @@ vtodo_class2 = "(Attività confidenziale)";
|
||||
= "%{Attendee} %{SentByText}ha delegato l'invito a %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}non ha ancora deciso riguardo il tuo invito all'evento.";
|
||||
|
||||
/* Resources */
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Numero massimo di prenotazioni simultanee (%{NumberOfSimultaneousBookings}) raggiunto per la risorsa \"%{Cn} %{SystemEmail}\".";
|
||||
@@ -344,7 +344,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
|
||||
NSMutableString *s;
|
||||
|
||||
s = [NSMutableString stringWithCapacity: 64];
|
||||
[s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
|
||||
[s appendFormat:@"<0x%08X[%@]:", (unsigned int)self, NSStringFromClass([self class])];
|
||||
if (freeBusyViewType)
|
||||
[s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType];
|
||||
if (mergedFreeBusy)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"Inviting the following persons is prohibited:" = "Канењето на следните лица е забрането:";
|
||||
"Personal Calendar" = "Личен календар";
|
||||
vevent_class0 = "(Јавен настан)";
|
||||
vevent_class1 = "(Приватен настан)";
|
||||
vevent_class2 = "(Доверлив настан)";
|
||||
|
||||
vtodo_class0 = "(Јавна задача)";
|
||||
vtodo_class1 = "(Приватна задача)";
|
||||
vtodo_class2 = "(Доверлива задача)";
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Настанот \"%{Summary}\" беше креиран";
|
||||
"The event \"%{Summary}\" was deleted" = "Настанот \"%{Summary}\" беше избришан";
|
||||
"The event \"%{Summary}\" was updated" = "Настанот \"%{Summary}\" беше освежен";
|
||||
"The following attendees(s) were notified" = "Следните учесници беа известини";
|
||||
"The following attendees(s) were added" = "Следните учесници беа додадени";
|
||||
"The following attendees(s) were removed" = "Следните учесници беа отстранети";
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Календар:";
|
||||
"startDate_label" = "Почеток:";
|
||||
"endDate_label" = "Крај:";
|
||||
"due_label" = "Краен датум:";
|
||||
"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\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}ве покани на %{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"
|
||||
= "Состанокот \"%{Summary}\" за %{OldStartDate} е променет";
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
|
||||
= "Состанокот \"%{Summary}\" за %{OldStartDate} на %{OldStartTime} се промени";
|
||||
"The following parameters have changed in the \"%{Summary}\" meeting:"
|
||||
= "Следните параметри се променија во \"%{Summary}\" состанок:";
|
||||
"Please accept or decline those changes."
|
||||
= "Прифатете ги ли отфрлете ги измените.";
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Прифатена покана: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Одбиена покана: \"%{Summary}\"";
|
||||
"Delegated invitation: \"%{Summary}\"" = "Делегирана покана: \"%{Summary}\"";
|
||||
"Not yet decided on invitation: \"%{Summary}\"" = "Уште не одлучиле по поканата: \"%{Summary}\"";
|
||||
"%{Attendee} %{SentByText}has accepted your event invitation."
|
||||
= "%{Attendee} %{SentByText}ја прифати вашата покана.";
|
||||
"%{Attendee} %{SentByText}has declined your event invitation."
|
||||
= "%{Attendee} %{SentByText}ја одби вашаа покана.";
|
||||
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
|
||||
= "%{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 = "(Konfidensiell hendelse)";
|
||||
vtodo_class0 = "(Offentlig oppgave)";
|
||||
vtodo_class1 = "(Privat oppgave)";
|
||||
vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Hendelsen \"%{Summary}\" ble opprettet";
|
||||
"The event \"%{Summary}\" was deleted" = "Hendelsen \"%{Summary}\" ble slettet";
|
||||
@@ -15,7 +14,6 @@ vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
"The following attendees(s) were notified" = "Følgende deltaker(e) ble varslet";
|
||||
"The following attendees(s) were added" = "Følgende deltaker(e) ble lagt til";
|
||||
"The following attendees(s) were removed" = "Følgende deltaker(e) ble fjernet";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalender";
|
||||
"startDate_label" = "Start";
|
||||
@@ -24,20 +22,17 @@ vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
"location_label" = "Sted";
|
||||
"summary_label" = "Sammendrag:";
|
||||
"comment_label" = "Kommentar:";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Hendelseinvitasjon: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(sendt av %{SentBy})";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} har invitert deg til %{Summary}.\n\nStart: %{StartDate}\nSlutt: %{EndDate}\nBeskrivelse: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} har invitert deg til %{Summary}.\n\nStart: %{StartDate} klokken %{StartTime}\nSlutt: %{EndDate} klokken %{EndTime}\nBeskrivelse: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Hendelse avlyst: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} har avlyst hendelsen: %{Summary}.\n\nStart: %{StartDate}\nSlutt: %{EndDate}\nBeskrivelse: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} har avlyst : %{Summary}.\n\nStart: %{StartDate} klokken %{StartTime}\nEnd: %{EndDate} klokken %{EndTime}\nBeskrivelse: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Avtalen \"%{Summary}\", %{OldStartDate} har endret seg";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
= "Følgende parametre har endret seg for \"%{Summary}\" møtet:";
|
||||
"Please accept or decline those changes."
|
||||
= "Vennligst godta eller avvis endringene.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Aksepterte invitasjonen: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Avviste invitasjonen: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
= "%{Attendee} %{SentByText} har delegert invitasjonen til %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText} har enda ikke svart på invitasjonen din.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Har ikke tilgang til ressurs: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimum antall samtidige reservasjoner (%{NumberOfSimultaneousBookings}) er nådd for ressursen \"%{Cn} %{SystemEmail}\". Den kolliderende hendelsen er \"%{EventTitle}\", og starter %{StartDate}.";
|
||||
@@ -6,24 +6,19 @@ vevent_class2 = "(Konfidensiell hendelse)";
|
||||
vtodo_class0 = "(Offentlig oppgave)";
|
||||
vtodo_class1 = "(Privat oppgave)";
|
||||
vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
|
||||
/* Receipts */
|
||||
"Title:" = "Tittel:";
|
||||
"Start:" = "Start:";
|
||||
"End:" = "Slutt:";
|
||||
|
||||
"Receipt: users invited to a meeting" = "Kvittering: brukere invitert til et møte";
|
||||
"You have invited the following attendees(s):" = "Du har invitert følende deltagere:";
|
||||
"... to attend the following event:" = "... til å delta i følgende hendelse:";
|
||||
|
||||
"Receipt: invitation updated" = "Kvittering: invitasjon oppdatert";
|
||||
"The following attendees(s):" = "Følgende deltager(e):";
|
||||
"... have been notified of the changes to the following event:" = "... har blitt informert om endringene i følgende hendelse:";
|
||||
|
||||
"Receipt: attendees removed from an event" = "Kvittering: deltagere fjernet fra en hendelse";
|
||||
"You have removed the following attendees(s):" = "Du har tatt bort følgende deltagere:";
|
||||
"... from the following event:" = "... fra følgende hendelse:";
|
||||
|
||||
/* IMIP messages */
|
||||
"startDate_label" = "Start";
|
||||
"endDate_label" = "End";
|
||||
@@ -31,17 +26,14 @@ vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
"location_label" = "Location";
|
||||
"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} 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} 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} at %{OldStartTime} has changed"
|
||||
= "The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed";
|
||||
@@ -49,7 +41,6 @@ vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
= "The following parameters have changed in the \"%{Summary}\" meeting:";
|
||||
"Please accept or decline those changes."
|
||||
= "Please accept or decline those changes.";
|
||||
|
||||
/* Reply */
|
||||
"%{Attendee} %{SentByText}has accepted your event invitation."
|
||||
= "%{Attendee} %{SentByText}has accepted your event invitation.";
|
||||
@@ -59,6 +50,5 @@ vtodo_class2 = "(Konfidensiell oppgave)";
|
||||
= "%{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 */
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\".";
|
||||
@@ -7,7 +7,6 @@ vevent_class2 = "(Wydarzenie poufne)";
|
||||
vtodo_class0 = "(Zadanie publiczne)";
|
||||
vtodo_class1 = "(Zadanie prywatne)";
|
||||
vtodo_class2 = "(Zadanie poufne)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Utworzono wydarzenie \"%{Summary}\"";
|
||||
"The event \"%{Summary}\" was deleted" = "Skasowano wydarzenie \"%{Summary}\"";
|
||||
@@ -15,29 +14,25 @@ vtodo_class2 = "(Zadanie poufne)";
|
||||
"The following attendees(s) were notified" = "Następujący uczestnicy zostali powiadomieni";
|
||||
"The following attendees(s) were added" = "Następujący uczestnicy zostali dodani";
|
||||
"The following attendees(s) were removed" = "Następujący uczestnicy zostali usunięci";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalendarz";
|
||||
"startDate_label" = "Początek";
|
||||
"endDate_label" = "Koniec";
|
||||
"due_label" = "Termin:";
|
||||
"due_label" = "Termin";
|
||||
"location_label" = "Miejsce";
|
||||
"summary_label" = "Podsumowanie:";
|
||||
"comment_label" = "Komentarz:";
|
||||
|
||||
"summary_label" = "Podsumowanie";
|
||||
"comment_label" = "Komentarz";
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Zaproszenie na wydarzenie: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(wysłane przez %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}zaprosił Cię na %{Summary}.⏎ ⏎ Początek: %{StartDate}⏎ Koniec: %{EndDate}⏎ Opis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}zaprosił cię na %{Summary}.\n\nPoczątek: %{StartDate} o %{StartTime}\nKoniec: %{EndDate} o %{EndTime}\nOpis: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Wydarzenie anulowane: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText} anulował(a) to wydarzenie: %{Summary}.⏎⏎ Początek: %{StartDate}⏎ Koniec: %{EndDate}⏎ Opis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}anulował(a) to wydarzenie: %{Summary}.\n\nPoczątek: %{StartDate} o %{StartTime}\nKoniec: %{EndDate} o %{EndTime}\nOpis: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Wydarzenie \"%{Summary}\" z dnia %{OldStartDate} zmieniło się";
|
||||
@@ -47,7 +42,6 @@ vtodo_class2 = "(Zadanie poufne)";
|
||||
= "Zmienione zostały poniższe parametry spotkania \"%{Summary}\":";
|
||||
"Please accept or decline those changes."
|
||||
= "Zaakceptuj lub odrzuć te zmiany.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Zaakceptowano zaproszenie: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Odrzucono zaproszenie: \"%{Summary}\"";
|
||||
@@ -61,7 +55,6 @@ vtodo_class2 = "(Zadanie poufne)";
|
||||
= "%{Attendee} %{SentByText}oddelegował(a) %{Delegate} na twoje wydarzenie.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}jeszcze nie zdecydował(a) o obecności na twoim wydarzeniu.";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Nie ma dostępu do: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Osiągnięto maksymalną liczbę równoległych rezerwacji (%{NumberOfSimultaneousBookings}) dla \"%{Cn} %{SystemEmail}\". Problem wywołuje wydarzenie \"%{EventTitle}\" zaczynające się w %{StartDate}.";
|
||||
@@ -0,0 +1,60 @@
|
||||
"Inviting the following persons is prohibited:" = "Convidando as seguintes pessoas é proibido:";
|
||||
"Personal Calendar" = "Calendário Pessoal";
|
||||
vevent_class0 = "(Evento Público)";
|
||||
vevent_class1 = "(Evento Privado)";
|
||||
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";
|
||||
"The event \"%{Summary}\" was updated" = "O evento \"%{Summary}\" foi atualizado";
|
||||
"The following attendees(s) were notified:" = "Os seguintes participantes foram notificados:";
|
||||
"The following attendees(s) were added:" = "Os seguintes participantes foram adicionados:";
|
||||
"The following attendees(s) were removed:" = "Os seguintes participantes foram removidos:";
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Calendário:";
|
||||
"startDate_label" = "Início:";
|
||||
"endDate_label" = "Fim:";
|
||||
"due_label" = "Data de Vencimento:";
|
||||
"location_label" = "Local:";
|
||||
"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-o 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-o 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} às %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "O compromisso \"%{Summary}\" de %{OldStartDate} mudou";
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
|
||||
= "O Compromisso \"%{Summary}\" de %{OldStartDate} as %{OldStartTime} mudou";
|
||||
"The following parameters have changed in the \"%{Summary}\" meeting:"
|
||||
= "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 aceite: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Convite recusado: \"%{Summary}\"";
|
||||
"Delegated invitation: \"%{Summary}\"" = "Convite delegado: \"%{Summary}\"";
|
||||
"Not yet decided on invitation: \"%{Summary}\"" = "Convite ainda não decidido: \"%{Summary}\"";
|
||||
"%{Attendee} %{SentByText}has accepted your event invitation."
|
||||
= "%{Attendee} %{SentByText} aceitou seu convite ao evento.";
|
||||
"%{Attendee} %{SentByText}has declined your event invitation."
|
||||
= "%{Attendee} %{SentByText} rejeitou seu convite ao evento.";
|
||||
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
|
||||
= "%{Attendee} %{SentByText} delegou o convite para %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText} ainda não decidiu seu convite ao evento.";
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Não foi possível aceder ao 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 simultâneas (%{NumberOfSimultaneousBookings}) acabou para o recurso \"%{Cn} %{SystemEmail}\". O evento em conflito é \"%{EventTitle}\", e inicia em %{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\Начало: %{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"
|
||||
= "Встреча \"%{Summary}\" назначенная на %{OldStartDate} была изменена";
|
||||
@@ -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}." = "Достигнуто предельное число заявок (%{NumberOfSimultaneousBookings}) на ресурс \"%{Cn} %{SystemEmail}\". Конкурирующее событие называется \"%{EventTitle}\", оно начинается %{StartDate}.";
|
||||
@@ -65,7 +65,6 @@ typedef enum {
|
||||
NSMutableDictionary *uidToFilename;
|
||||
NSMutableDictionary *aclMatrix;
|
||||
NSMutableArray *stripFields;
|
||||
NSString *baseCalDAVURL, *basePublicCalDAVURL;
|
||||
int davCalendarStartTimeLimit;
|
||||
int davTimeLimitSeconds;
|
||||
int davTimeHalfLimitSeconds;
|
||||
@@ -74,7 +73,6 @@ typedef enum {
|
||||
}
|
||||
|
||||
- (BOOL) isActive;
|
||||
- (BOOL) isWebCalendar;
|
||||
|
||||
- (NSString *) calendarColor;
|
||||
- (void) setCalendarColor: (NSString *) newColor;
|
||||
@@ -131,13 +129,6 @@ typedef enum {
|
||||
- (NSString *) baseURLForAptWithUID: (NSString *) _uid
|
||||
inContext: (id) _ctx;
|
||||
|
||||
- (NSString *) calDavURL;
|
||||
- (NSString *) webDavICSURL;
|
||||
- (NSString *) webDavXMLURL;
|
||||
- (NSString *) publicCalDavURL;
|
||||
- (NSString *) publicWebDavICSURL;
|
||||
- (NSString *) publicWebDavXMLURL;
|
||||
|
||||
/* folder management */
|
||||
|
||||
- (id) lookupHomeFolderForUID: (NSString *) _uid
|
||||
|
||||
Loaded 100 of 1452 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user