Compare commits
235
Commits
SOGo-2.2.0
...
SOGo-2.2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95d6911f1a | ||
|
|
dace1439df | ||
|
|
d91b37413f | ||
|
|
bd916f06eb | ||
|
|
ecbe0858f7 | ||
|
|
24095a464c | ||
|
|
9ce3b8ff8f | ||
|
|
efc33d8174 | ||
|
|
93971dc434 | ||
|
|
fac9520f64 | ||
|
|
9eb075cc3f | ||
|
|
c5922b07aa | ||
|
|
51e07fc306 | ||
|
|
d35c52bb38 | ||
|
|
6bab4c94f2 | ||
|
|
c31429a5d5 | ||
|
|
04bb88a470 | ||
|
|
c79cd750d8 | ||
|
|
6fc81e61ec | ||
|
|
d9ad286650 | ||
|
|
dd57d56cf9 | ||
|
|
ab7780d617 | ||
|
|
cb9332f240 | ||
|
|
7b60ad4797 | ||
|
|
3261c21549 | ||
|
|
364b409501 | ||
|
|
064565e1a9 | ||
|
|
f92d280fbb | ||
|
|
ea2bce706c | ||
|
|
d74afaf295 | ||
|
|
5e7beb3345 | ||
|
|
2df5887eec | ||
|
|
3d1bdf1a09 | ||
|
|
5de1343506 | ||
|
|
1eaa3f3891 | ||
|
|
c2d3a827bd | ||
|
|
6b72a8d126 | ||
|
|
819d0641b7 | ||
|
|
5d7f4ba6c6 | ||
|
|
34a7e28858 | ||
|
|
87b010c700 | ||
|
|
a44b183b7e | ||
|
|
b488721f83 | ||
|
|
8a03f3bd63 | ||
|
|
f8c8abc989 | ||
|
|
db8d9bb16c | ||
|
|
2794425a3f | ||
|
|
2f7979c796 | ||
|
|
c426afd7f2 | ||
|
|
07445eb069 | ||
|
|
93bf98c326 | ||
|
|
c08c3e27d5 | ||
|
|
c8860dc92b | ||
|
|
1a1543a380 | ||
|
|
d2d5467317 | ||
|
|
18443de9bf | ||
|
|
be72456a9a | ||
|
|
a54b3e4289 | ||
|
|
7a4feae8b8 | ||
|
|
99d49b9bd9 | ||
|
|
5f9fb4e1c4 | ||
|
|
ef2188962a | ||
|
|
2922f15ca7 | ||
|
|
41e6fb89d3 | ||
|
|
c4a3843695 | ||
|
|
bf67b6435f | ||
|
|
b5319446db | ||
|
|
d58184af15 | ||
|
|
fbc6856fd5 | ||
|
|
5c41915080 | ||
|
|
698524b39f | ||
|
|
1f5a2a4c62 | ||
|
|
fc3cfa5484 | ||
|
|
593b44ed01 | ||
|
|
b2ed7e3f6b | ||
|
|
bc2b41f383 | ||
|
|
7d7961ffcf | ||
|
|
0b9afec635 | ||
|
|
4e42a6a689 | ||
|
|
da8febf138 | ||
|
|
8c563c2922 | ||
|
|
fa5cf55367 | ||
|
|
e43b704858 | ||
|
|
44a4baca7f | ||
|
|
78447d81ae | ||
|
|
9dc07fccc0 | ||
|
|
ba7b65cf7b | ||
|
|
66bfbeaac1 | ||
|
|
619cb2c0ec | ||
|
|
f9869d19b4 | ||
|
|
57b747c24a | ||
|
|
8f6b0f80a7 | ||
|
|
cb5e6e7ef2 | ||
|
|
49363cfe36 | ||
|
|
c345fc2b8d | ||
|
|
d200629b01 | ||
|
|
cfc1ac6b3b | ||
|
|
e66ceaad07 | ||
|
|
f370ac114a | ||
|
|
75ee6b4806 | ||
|
|
0961fbbab3 | ||
|
|
0d1c39ca71 | ||
|
|
5739356f88 | ||
|
|
33467093c1 | ||
|
|
96ece873ca | ||
|
|
8909775cf6 | ||
|
|
0ba59de88e | ||
|
|
4fbe937f6d | ||
|
|
ab80e87a3f | ||
|
|
068eb921aa | ||
|
|
5c13fade22 | ||
|
|
371e5ead08 | ||
|
|
c5e6ccf332 | ||
|
|
ec9d48c3a9 | ||
|
|
877082f042 | ||
|
|
342b68fd44 | ||
|
|
e24c22e3dc | ||
|
|
7997448d41 | ||
|
|
3629670fca | ||
|
|
c250b9a6d4 | ||
|
|
afa564a6a6 | ||
|
|
d9b99f536c | ||
|
|
5699e5e1b4 | ||
|
|
e869406081 | ||
|
|
3e221253ad | ||
|
|
446ef3467a | ||
|
|
bf64076b87 | ||
|
|
c9cbcf2566 | ||
|
|
4fe0358581 | ||
|
|
534e4e3d4d | ||
|
|
09bcd2aee2 | ||
|
|
526e21e837 | ||
|
|
c80e222210 | ||
|
|
6e60bbb553 | ||
|
|
846212336a | ||
|
|
349f4d824a | ||
|
|
2f565d2ec9 | ||
|
|
eb7cabac71 | ||
|
|
2ff4498983 | ||
|
|
3eec753b73 | ||
|
|
7b0ffacff0 | ||
|
|
f999c87d4d | ||
|
|
48f98ef34e | ||
|
|
378f2b5876 | ||
|
|
d091b3d7cd | ||
|
|
370e1e6d26 | ||
|
|
e2b3d5a7ff | ||
|
|
078b88a396 | ||
|
|
ee09b80fc3 | ||
|
|
99d9615c92 | ||
|
|
79c3fa8ad2 | ||
|
|
fd07112113 | ||
|
|
ce5d591d1d | ||
|
|
e603742d71 | ||
|
|
585d224a15 | ||
|
|
6a39b48a37 | ||
|
|
633723a470 | ||
|
|
ba32b95308 | ||
|
|
03d5975be3 | ||
|
|
1da1764aae | ||
|
|
6e4f776d4b | ||
|
|
365e271a84 | ||
|
|
bce701208a | ||
|
|
b16911edc1 | ||
|
|
76307cfaaf | ||
|
|
e5fb7dc7b8 | ||
|
|
50e7ea7188 | ||
|
|
ae0c5eae77 | ||
|
|
89e61f6d5d | ||
|
|
0c2d727057 | ||
|
|
85f4af56e9 | ||
|
|
04bdb22b00 | ||
|
|
874b1d9eba | ||
|
|
b143688419 | ||
|
|
5f3d2d988e | ||
|
|
93f1540c17 | ||
|
|
877e44c6d2 | ||
|
|
4ca3d351e0 | ||
|
|
19b6704955 | ||
|
|
64787555ff | ||
|
|
27ffcf240f | ||
|
|
893d75bd5f | ||
|
|
b0f26f1dcc | ||
|
|
a196ed210e | ||
|
|
1c28a4b4d2 | ||
|
|
fc56493b78 | ||
|
|
3a33010918 | ||
|
|
83bccda551 | ||
|
|
35d71f6150 | ||
|
|
e92a145325 | ||
|
|
c3062284e3 | ||
|
|
e2f192b793 | ||
|
|
b8f239bf34 | ||
|
|
e968be8024 | ||
|
|
3c3bebeee3 | ||
|
|
26e9defdbd | ||
|
|
8742f28c29 | ||
|
|
bd577fd513 | ||
|
|
c8550c8a4a | ||
|
|
1bdf88fb67 | ||
|
|
d108777e89 | ||
|
|
167a06e3e9 | ||
|
|
af68f10115 | ||
|
|
cbd22762c3 | ||
|
|
8cc352fb28 | ||
|
|
0d69a476bc | ||
|
|
cee1e529f8 | ||
|
|
3a681bd5a5 | ||
|
|
f5faa60814 | ||
|
|
f8939bfb3e | ||
|
|
78636f0396 | ||
|
|
037c2d3ddf | ||
|
|
072f5f5a15 | ||
|
|
41fe9195f2 | ||
|
|
23789b48bc | ||
|
|
142296f71e | ||
|
|
3bae2387d4 | ||
|
|
6cecca6c4f | ||
|
|
28b1938bd9 | ||
|
|
f4d189ffc8 | ||
|
|
1b9ac03e94 | ||
|
|
6ad87052ba | ||
|
|
e7a16cba0d | ||
|
|
31ace947cb | ||
|
|
0cea36df2e | ||
|
|
afd7452323 | ||
|
|
e4a21bcef5 | ||
|
|
0c99323db3 | ||
|
|
da208d5b4a | ||
|
|
158af71152 | ||
|
|
163bce39b5 | ||
|
|
02c72fee6a | ||
|
|
8bef84bded | ||
|
|
8db3b22eb9 | ||
|
|
1aabcf04b5 |
No files matched your search
@@ -1,6 +1,8 @@
|
||||
config.make
|
||||
tags
|
||||
*._*
|
||||
*/obj/
|
||||
*._*
|
||||
*/*/obj/
|
||||
*/*/*/obj/
|
||||
*/*/*.SOGo/
|
||||
@@ -11,3 +13,4 @@ SoObjects/SOGo/SOGo.framework/
|
||||
SoObjects/SOGo/derived_src/
|
||||
Tests/*/config.py
|
||||
*.pyc
|
||||
._*
|
||||
+26
@@ -235,6 +235,32 @@ trans.sk = SoObjects/Contacts/Slovak.lproj/Localizable.strings
|
||||
trans.sv = SoObjects/Contacts/Swedish.lproj/Localizable.strings
|
||||
trans.uk = SoObjects/Contacts/Ukrainian.lproj/Localizable.strings
|
||||
|
||||
[sogo.soobjects-mailer]
|
||||
source_file = SoObjects/Mailer/English.lproj/Localizable.strings
|
||||
source_lang = en
|
||||
trans.ar = SoObjects/Mailer/Arabic.lproj/Localizable.strings
|
||||
trans.ca = SoObjects/Mailer/Catalan.lproj/Localizable.strings
|
||||
trans.cs = SoObjects/Mailer/Czech.lproj/Localizable.strings
|
||||
trans.cy = SoObjects/Mailer/Welsh.lproj/Localizable.strings
|
||||
trans.da_DK = SoObjects/Mailer/Danish.lproj/Localizable.strings
|
||||
trans.de = SoObjects/Mailer/German.lproj/Localizable.strings
|
||||
trans.es_AR = SoObjects/Mailer/SpanishArgentina.lproj/Localizable.strings
|
||||
trans.es_ES = SoObjects/Mailer/SpanishSpain.lproj/Localizable.strings
|
||||
trans.fi = SoObjects/Mailer/Finnish.lproj/Localizable.strings
|
||||
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.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_BR = SoObjects/Mailer/BrazilianPortuguese.lproj/Localizable.strings
|
||||
trans.ru = SoObjects/Mailer/Russian.lproj/Localizable.strings
|
||||
trans.sk = SoObjects/Mailer/Slovak.lproj/Localizable.strings
|
||||
trans.sv = SoObjects/Mailer/Swedish.lproj/Localizable.strings
|
||||
trans.uk = SoObjects/Mailer/Ukrainian.lproj/Localizable.strings
|
||||
|
||||
[sogo.ui-mailpartviewers]
|
||||
source_file = UI/MailPartViewers/English.lproj/Localizable.strings
|
||||
source_lang = en
|
||||
|
||||
@@ -9,28 +9,31 @@ ActiveSync_PRINCIPAL_CLASS = ActiveSyncProduct
|
||||
|
||||
ActiveSync_OBJC_FILES = \
|
||||
ActiveSyncProduct.m \
|
||||
iCalAlarm+ActiveSync.m \
|
||||
iCalEvent+ActiveSync.m \
|
||||
iCalRecurrenceRule+ActiveSync.m \
|
||||
iCalTimeZone+ActiveSync.m \
|
||||
iCalToDo+ActiveSync.m \
|
||||
NSCalendarDate+ActiveSync.m \
|
||||
NSCalendarDate+ActiveSync.m \
|
||||
NSData+ActiveSync.m \
|
||||
NSDate+ActiveSync.m \
|
||||
NGDOMElement+ActiveSync.m \
|
||||
NGMimeMessage+ActiveSync.m \
|
||||
NGVCard+ActiveSync.m \
|
||||
NSArray+SyncCache.m \
|
||||
NSString+ActiveSync.m \
|
||||
SOGoActiveSyncDispatcher.m \
|
||||
SOGoActiveSyncDispatcher+Sync.m \
|
||||
SOGoMailObject+ActiveSync.m \
|
||||
SOGoMailObject+ActiveSync.m \
|
||||
SOGoSyncCacheObject.m \
|
||||
SoObjectWebDAVDispatcher+ActiveSync.m
|
||||
|
||||
ActiveSync_RESOURCE_FILES += \
|
||||
product.plist
|
||||
|
||||
ADDITIONAL_OBJCFLAGS += -Wno-deprecated-declarations
|
||||
ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/ -I../SoObjects/
|
||||
ADDITIONAL_LIB_DIRS += -L../../SOPE/GDLContentStore/obj/
|
||||
ADDITIONAL_INCLUDE_DIRS += -I../SOPE/ -I../SoObjects/
|
||||
ADDITIONAL_LIB_DIRS += -L../SOPE/GDLContentStore/obj/ -L../SOPE/NGCards/obj/
|
||||
ADDITIONAL_INCLUDE_DIRS += -I/usr/include/libwbxml-1.0/
|
||||
ADDITIONAL_LDFLAGS += -Wl,--no-as-needed -lwbxml2
|
||||
|
||||
|
||||
@@ -37,6 +37,28 @@ static NSArray *asElementArray = nil;
|
||||
|
||||
@implementation NGDOMElement (ActiveSync)
|
||||
|
||||
- (BOOL) isTextNode
|
||||
{
|
||||
id <DOMNodeList> children;
|
||||
id <DOMElement> element;
|
||||
int i;
|
||||
|
||||
if ([self nodeType] == DOM_TEXT_NODE)
|
||||
return YES;
|
||||
|
||||
children = [self childNodes];
|
||||
|
||||
for (i = 0; i < [children length]; i++)
|
||||
{
|
||||
element = [children objectAtIndex: i];
|
||||
|
||||
if ([element nodeType] != DOM_TEXT_NODE)
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
//
|
||||
// We must handle "inner data" like this:
|
||||
//
|
||||
@@ -79,7 +101,7 @@ static NSArray *asElementArray = nil;
|
||||
int i, count;
|
||||
|
||||
if (!asElementArray)
|
||||
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", nil];
|
||||
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil];
|
||||
|
||||
data = [NSMutableDictionary dictionary];
|
||||
|
||||
@@ -96,9 +118,17 @@ static NSArray *asElementArray = nil;
|
||||
|
||||
tag = [element tagName];
|
||||
count = [(NSArray *)[element childNodes] count];
|
||||
|
||||
|
||||
// We check if the node is a text one or if all its
|
||||
// children are text nodes. This is important to avoid side-effects
|
||||
// in SOPE where "foo & bar" would result into 3 childnodes instead
|
||||
// of just one.
|
||||
if ([(id)element isTextNode])
|
||||
{
|
||||
value = [(id)element textValue];
|
||||
}
|
||||
// Handle inner data - see above for samples
|
||||
if (count > 2)
|
||||
else
|
||||
{
|
||||
NSMutableArray *innerElements;
|
||||
id <DOMElement> innerElement;
|
||||
@@ -123,7 +153,10 @@ static NSArray *asElementArray = nil;
|
||||
|
||||
if ([innerTag isEqualToString: [innerElement tagName]])
|
||||
{
|
||||
[innerElements addObject: [(NGDOMElement *)innerElement applicationData]];
|
||||
if ([(id)innerElement isTextNode])
|
||||
[innerElements addObject: [(NGDOMElement *)innerElement textValue]];
|
||||
else
|
||||
[innerElements addObject: [(NGDOMElement *)innerElement applicationData]];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -144,12 +177,11 @@ static NSArray *asElementArray = nil;
|
||||
value = nil;
|
||||
}
|
||||
}
|
||||
else
|
||||
value = [[element firstChild] textValue];
|
||||
|
||||
if (value && tag)
|
||||
[data setObject: value forKey: tag];
|
||||
}
|
||||
|
||||
} // if ([element nodeType] == DOM_ELEMENT_NODE)
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
@@ -47,9 +47,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *emails, *addresses, *categories, *elements;
|
||||
CardElement *n, *homeAdr, *workAdr;
|
||||
NSArray *emails, *addresses;
|
||||
NSMutableString *s;
|
||||
NSString *url;
|
||||
id o;
|
||||
|
||||
int i;
|
||||
@@ -65,10 +66,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if ((o = [self workCompany]))
|
||||
[s appendFormat: @"<CompanyName xmlns=\"Contacts:\">%@</CompanyName>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [[self org] flattenedValueAtIndex: 1 forKey: @""]))
|
||||
[s appendFormat: @"<Department xmlns=\"Contacts:\">%@</Department>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
categories = [self categories];
|
||||
|
||||
if ([categories count])
|
||||
{
|
||||
[s appendFormat: @"<Categories xmlns=\"Contacts:\">"];
|
||||
for (i = 0; i < [categories count]; i++)
|
||||
{
|
||||
[s appendFormat: @"<Category xmlns=\"Contacts:\">%@</Category>", [[categories objectAtIndex: i] activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
[s appendFormat: @"</Categories>"];
|
||||
}
|
||||
|
||||
elements = [self childrenWithTag: @"url"
|
||||
andAttribute: @"type"
|
||||
havingValue: @"work"];
|
||||
if ([elements count] > 0)
|
||||
{
|
||||
url = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
|
||||
[s appendFormat: @"<WebPage xmlns=\"Contacts:\">%@</WebPage>", [url activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
|
||||
if ((o = [[self uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""]))
|
||||
[s appendFormat: @"<IMAddress xmlns=\"Contacts:\">%@</IMAddress>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [self nickname]))
|
||||
[s appendFormat: @"<NickName xmlns=\"Contacts:\">%@</NickName>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
|
||||
if ((o = [self title]))
|
||||
[s appendFormat: @"<JobTitle xmlns=\"Contacts:\">%@</JobTitle>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
|
||||
if ((o = [self preferredEMail]))
|
||||
[s appendFormat: @"<Email1Address xmlns=\"Contacts:\">%@</Email1Address>", o];
|
||||
|
||||
@@ -154,7 +187,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Other, less important fields
|
||||
if ((o = [self birthday]))
|
||||
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>", [o activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [self note]))
|
||||
{
|
||||
@@ -183,6 +216,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"]))
|
||||
[self setNote: o];
|
||||
|
||||
// Categories
|
||||
if ((o = [theValues objectForKey: @"Categories"]))
|
||||
[self setCategories: o];
|
||||
|
||||
// Birthday
|
||||
if ((o = [theValues objectForKey: @"Birthday"]))
|
||||
{
|
||||
@@ -238,24 +275,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Company's name
|
||||
if ((o = [theValues objectForKey: @"CompanyName"]))
|
||||
{
|
||||
[self setOrg: o units: nil];
|
||||
}
|
||||
[self setOrg: o units: nil];
|
||||
|
||||
// Department
|
||||
if ((o = [theValues objectForKey: @"Department"]))
|
||||
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
|
||||
|
||||
// Email addresses
|
||||
if ((o = [theValues objectForKey: @"Email1Address"]))
|
||||
{
|
||||
[self addEmail: o types: [NSArray arrayWithObject: @"pref"]];
|
||||
element = [self elementWithTag: @"email" ofType: @"work"];
|
||||
[element setSingleValue: o forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"Email2Address"]))
|
||||
{
|
||||
[self addEmail: o types: nil];
|
||||
element = [self elementWithTag: @"email" ofType: @"home"];
|
||||
[element setSingleValue: o forKey: @""];
|
||||
}
|
||||
|
||||
// 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"]))
|
||||
{
|
||||
[self addEmail: o types: nil];
|
||||
element = [self elementWithTag: @"email" ofType: @"three"];
|
||||
[element setSingleValue: o forKey: @""];
|
||||
}
|
||||
|
||||
// Formatted name
|
||||
@@ -293,16 +337,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Job's title
|
||||
if ((o = [theValues objectForKey: @"JobTitle"]))
|
||||
{
|
||||
[self setTitle: o];
|
||||
}
|
||||
[self setTitle: o];
|
||||
|
||||
// WebPage (work)
|
||||
if ((o = [theValues objectForKey: @"WebPage"]))
|
||||
{
|
||||
[[self elementWithTag: @"url" ofType: @"work"]
|
||||
[[self elementWithTag: @"url" ofType: @"work"]
|
||||
setSingleValue: o forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"NickName"]))
|
||||
[self setNickname: o];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Inverse inc. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
#ifndef __NSARRAYSYNCCACHE_H__
|
||||
#define __NSARRAYSYNCCACHE_H__
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
|
||||
@class NSDictionary;
|
||||
|
||||
@interface NSMutableArray (SyncCache)
|
||||
|
||||
- (id) initWithDictionary: (NSDictionary *) theDictionary;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSArray (SyncCache)
|
||||
|
||||
- (NSDictionary *) dictionaryValue;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Inverse inc. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
#import "NSArray+SyncCache.h"
|
||||
|
||||
#import <Foundation/NSDictionary.h>
|
||||
|
||||
#include "SOGoSyncCacheObject.h"
|
||||
|
||||
@implementation NSMutableArray (SyncCache)
|
||||
|
||||
- (id) initWithDictionary: (NSDictionary *) theDictionary
|
||||
{
|
||||
SOGoSyncCacheObject *o;
|
||||
NSArray *allKeys;
|
||||
id key;
|
||||
int i;
|
||||
|
||||
self = [self initWithCapacity: [theDictionary count]];
|
||||
|
||||
allKeys = [theDictionary allKeys];
|
||||
|
||||
for (i = 0; i < [allKeys count]; i++)
|
||||
{
|
||||
key = [allKeys objectAtIndex: i];
|
||||
o = [SOGoSyncCacheObject syncCacheObjectWithUID: key
|
||||
sequence: [theDictionary objectForKey: key]];
|
||||
[self addObject: o];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@implementation NSArray (SyncCache)
|
||||
|
||||
- (NSDictionary *) dictionaryValue
|
||||
{
|
||||
NSMutableDictionary *d;
|
||||
SOGoSyncCacheObject *o;
|
||||
int i;
|
||||
|
||||
d = [NSMutableDictionary dictionary];
|
||||
|
||||
for (i = 0; i < [self count]; i++)
|
||||
{
|
||||
o = [self objectAtIndex: i];
|
||||
[d setObject: [o sequence] forKey: [o uid]];
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -36,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
||||
{
|
||||
return [self descriptionWithCalendarFormat: @"%Y-%m-%d-T%H:%M:%S.%FZ" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
|
||||
return [self descriptionWithCalendarFormat: @"%Y-%m-%dT%H:%M:%S.%FZ" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- (NSString *) realCollectionIdWithFolderType: (SOGoMicrosoftActiveSyncFolderType *) folderType;
|
||||
- (NSCalendarDate *) calendarDate;
|
||||
- (NSString *) deviceId;
|
||||
- (NSString *) deviceType;
|
||||
- (NSString *) command;
|
||||
- (NSData *) convertHexStringToBytes;
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *s;
|
||||
int i;
|
||||
|
||||
components = [[[self componentsSeparatedByString: @"/"] lastObject] componentsSeparatedByString: @"&"];
|
||||
components = [[[self componentsSeparatedByString: @"?"] lastObject] componentsSeparatedByString: @"&"];
|
||||
|
||||
for (i = 0; i < [components count]; i++)
|
||||
{
|
||||
@@ -169,6 +169,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
return s;
|
||||
}
|
||||
|
||||
//
|
||||
// This method extracts the "DeviceType" from a URI:
|
||||
//
|
||||
// /SOGo/Microsoft-Server-ActiveSync?Cmd=FolderSync&User=sogo10&DeviceId=SEC17CD1A3E9E3F2&DeviceType=SAMSUNGSGHI317M
|
||||
//
|
||||
- (NSString *) deviceType
|
||||
{
|
||||
NSString *s;
|
||||
|
||||
s = [self _valueForParameter: @"DEVICETYPE="];
|
||||
|
||||
if (!s)
|
||||
s = @"Unknown";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
@@ -32,9 +32,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSNull.h>
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGObjWeb/SoApplication.h>
|
||||
@@ -58,18 +60,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <NGImap4/NSString+Imap4.h>
|
||||
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
#import <NGMail/NGMimeMessageParser.h>
|
||||
#import <NGMail/NGMimeMessage.h>
|
||||
#import <NGMail/NGMimeMessageGenerator.h>
|
||||
|
||||
#import <DOM/DOMElement.h>
|
||||
#import <DOM/DOMProtocols.h>
|
||||
|
||||
#import <EOControl/EOQualifier.h>
|
||||
|
||||
#import <SOGo/NSArray+DAV.h>
|
||||
#import <SOGo/SOGoCache.h>
|
||||
#import <SOGo/NSDictionary+DAV.h>
|
||||
#import <SOGo/SOGoDAVAuthenticator.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
@@ -77,6 +74,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/SOGoCacheGCSObject.h>
|
||||
|
||||
#import <Appointments/SOGoAppointmentObject.h>
|
||||
#import <Appointments/SOGoAppointmentFolder.h>
|
||||
@@ -106,11 +104,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "NSString+ActiveSync.h"
|
||||
#include "SOGoActiveSyncConstants.h"
|
||||
#include "SOGoMailObject+ActiveSync.h"
|
||||
#include "SOGoSyncCacheObject.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
@implementation SOGoActiveSyncDispatcher (Sync)
|
||||
|
||||
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
|
||||
forKey: (NSString *) theFolderKey
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
NSString *key;
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
[[o properties] removeObjectForKey: @"SyncCache"];
|
||||
[[o properties] removeObjectForKey: @"DateCache"];
|
||||
|
||||
[[o properties] addEntriesFromDictionary: theFolderMetadata];
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
NSString *key;
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
return [o properties];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// <?xml version="1.0"?>
|
||||
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
||||
@@ -392,7 +427,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
[sogoObject delete];
|
||||
if (![sogoObject isKindOfClass: [NSException class]])
|
||||
[sogoObject delete];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,7 +463,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[theBuffer appendString: @"</Fetch>"];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// The method handles <GetChanges/>
|
||||
//
|
||||
@@ -454,6 +489,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
return;
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
more_available = NO;
|
||||
|
||||
switch (theFolderType)
|
||||
@@ -571,67 +607,189 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
case ActiveSyncMailFolder:
|
||||
default:
|
||||
{
|
||||
NSMutableDictionary *syncCache, *dateCache, *folderMetadata;
|
||||
SOGoSyncCacheObject *lastCacheObject, *aCacheObject;
|
||||
NSMutableArray *allCacheObjects, *sortedBySequence;
|
||||
|
||||
SOGoMailObject *mailObject;
|
||||
NSString *uid, *command;
|
||||
NSDictionary *aMessage;
|
||||
NSArray *allMessages;
|
||||
|
||||
|
||||
int j, k, return_count;
|
||||
BOOL found_in_cache;
|
||||
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
|
||||
// Check for the WindowSize.
|
||||
// FIXME: we should eventually check for modseq and slice the maximum
|
||||
// amount of messages returned to ensure we don't have the same
|
||||
// modseq accross contiguous boundaries
|
||||
max = [allMessages count];
|
||||
if (max > theWindowSize)
|
||||
{
|
||||
max = theWindowSize;
|
||||
more_available = YES;
|
||||
}
|
||||
|
||||
allCacheObjects = [NSMutableArray array];
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
aMessage = [allMessages objectAtIndex: i];
|
||||
|
||||
uid = [[[aMessage allKeys] lastObject] stringValue];
|
||||
command = [[aMessage allValues] lastObject];
|
||||
|
||||
if ([command isEqualToString: @"deleted"])
|
||||
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
|
||||
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
||||
}
|
||||
|
||||
// If it's a new Sync operation, DateCache and SyncCache need to be deleted
|
||||
// but GUID stored by folderSync shouldn't be touched
|
||||
folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]];
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
|
||||
}
|
||||
// Check whether GUID in cache is equal to the GUID from imap - this is to avoid cache corruptions if a folder has been renamed and a new folder
|
||||
// with the same name has been created but folderSync has not yet updated the cache
|
||||
if (!([[theCollection nameInContainer] isEqualToString:
|
||||
[NSString stringWithFormat: @"folder%@", [self globallyUniqueIDToIMAPFolderName: [folderMetadata objectForKey: @"GUID"] type: theFolderType]]]))
|
||||
{
|
||||
NSLog(@"GUID mismatch don't sync now!");
|
||||
return;
|
||||
}
|
||||
|
||||
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
||||
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
||||
|
||||
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||
[sortedBySequence sortUsingSelector: @selector(compareSequence:)];
|
||||
[sortedBySequence autorelease];
|
||||
|
||||
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
|
||||
|
||||
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]);
|
||||
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]);
|
||||
|
||||
lastCacheObject = [sortedBySequence lastObject];
|
||||
|
||||
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject)
|
||||
{
|
||||
for (j = 0; j < [allCacheObjects count]; j++)
|
||||
{
|
||||
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", uid];
|
||||
[s appendString: @"</Delete>"];
|
||||
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
|
||||
{
|
||||
// Found out where we're at, let's continue from there...
|
||||
found_in_cache = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
found_in_cache = NO;
|
||||
|
||||
|
||||
if (found_in_cache)
|
||||
k = j+1;
|
||||
else
|
||||
{
|
||||
k = 0;
|
||||
j = 0;
|
||||
}
|
||||
|
||||
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
|
||||
|
||||
return_count = 0;
|
||||
|
||||
for (; k < [allCacheObjects count]; k++)
|
||||
{
|
||||
// Check for the WindowSize and slice accordingly
|
||||
if (return_count >= theWindowSize)
|
||||
{
|
||||
NSString *lastSequence;
|
||||
more_available = YES;
|
||||
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
|
||||
*theLastServerKey = [NSString stringWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
||||
break;
|
||||
}
|
||||
|
||||
aCacheObject = [allCacheObjects objectAtIndex: k];
|
||||
|
||||
// If found in cache, it's either a Change or a Delete
|
||||
if ([syncCache objectForKey: [aCacheObject uid]])
|
||||
{
|
||||
if ([[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
// Deleted
|
||||
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
[s appendString: @"</Delete>"];
|
||||
|
||||
[syncCache removeObjectForKey: [aCacheObject uid]];
|
||||
[dateCache removeObjectForKey: [aCacheObject uid]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Changed
|
||||
outlook_hack:
|
||||
mailObject = [theCollection lookupName: [aCacheObject uid]
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
|
||||
[s appendString: @"</ApplicationData>"];
|
||||
[s appendString: @"</Change>"];
|
||||
|
||||
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
||||
}
|
||||
|
||||
return_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([command isEqualToString: @"added"])
|
||||
[s appendString: @"<Add xmlns=\"AirSync:\">"];
|
||||
// Added
|
||||
if (![[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
NSString *key;
|
||||
|
||||
// We check for Outlook stupidity to avoid creating duplicates - see the comment
|
||||
// in SOGoActiveSyncDispatcher.m: -processMoveItems:inResponse: for more details.
|
||||
key = [NSString stringWithFormat: @"%@+%@+%@+%@",
|
||||
[[context activeUser] login],
|
||||
[context objectForKey: @"DeviceType"],
|
||||
[theCollection displayName],
|
||||
[aCacheObject uid]];
|
||||
|
||||
if ([[SOGoCache sharedCache] valueForKey: key])
|
||||
{
|
||||
[[SOGoCache sharedCache] removeValueForKey: key];
|
||||
goto outlook_hack;
|
||||
}
|
||||
|
||||
mailObject = [theCollection lookupName: [aCacheObject uid]
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
[s appendString: @"<Add xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
|
||||
[s appendString: @"</ApplicationData>"];
|
||||
[s appendString: @"</Add>"];
|
||||
|
||||
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
||||
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
|
||||
return_count++;
|
||||
}
|
||||
else
|
||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||
|
||||
mailObject = [theCollection lookupName: uid
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", uid];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
|
||||
[s appendString: @"</ApplicationData>"];
|
||||
|
||||
if ([command isEqualToString: @"added"])
|
||||
[s appendString: @"</Add>"];
|
||||
else
|
||||
[s appendString: @"</Change>"];
|
||||
|
||||
{
|
||||
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
if (more_available)
|
||||
{
|
||||
*theLastServerKey = uid;
|
||||
}
|
||||
}
|
||||
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
||||
|
||||
|
||||
[self _setFolderMetadata: folderMetadata
|
||||
forKey: [theCollection nameInContainer]];
|
||||
} // default:
|
||||
break;
|
||||
} // switch (folderType) ...
|
||||
|
||||
@@ -732,13 +890,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
BOOL getChanges, first_sync;
|
||||
unsigned int windowSize;
|
||||
unsigned int windowSize, v;
|
||||
|
||||
changeBuffer = [NSMutableString string];
|
||||
commandsBuffer = [NSMutableString string];
|
||||
|
||||
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue];
|
||||
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
|
||||
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
||||
collection = [self collectionFromId: realCollectionId type: folderType];
|
||||
|
||||
syncKey = davCollectionTag = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
@@ -748,6 +907,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (windowSize == 0 || windowSize > 512)
|
||||
windowSize = 100;
|
||||
|
||||
// We check if we must overwrite the windowSize with a system preference. This can be useful
|
||||
// if the user population has large mailboxes and slow connectivity
|
||||
if ((v = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncWindowSize]))
|
||||
windowSize = v;
|
||||
|
||||
lastServerKey = nil;
|
||||
|
||||
@@ -820,12 +984,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ([changeBuffer length] || [commandsBuffer length])
|
||||
{
|
||||
if (lastServerKey)
|
||||
davCollectionTag = [collection davCollectionTagFromId: lastServerKey];
|
||||
else
|
||||
davCollectionTag = lastServerKey;
|
||||
else if (![[self _folderMetadataForKey: [collection nameInContainer]] objectForKey: @"MoreAvailable"])
|
||||
davCollectionTag = [collection davCollectionTag];
|
||||
|
||||
*changeDetected = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (folderType == ActiveSyncMailFolder && [syncKey isEqualToString: @"-1"])
|
||||
davCollectionTag = [collection davCollectionTag];
|
||||
}
|
||||
|
||||
|
||||
// Generate the response buffer
|
||||
[theBuffer appendString: @"<Collection>"];
|
||||
|
||||
@@ -32,17 +32,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "SOGoActiveSyncConstants.h"
|
||||
|
||||
@class NSException;
|
||||
@class NSURL;
|
||||
|
||||
@interface SOGoActiveSyncDispatcher : NSObject
|
||||
{
|
||||
NSURL *folderTableURL;
|
||||
id context;
|
||||
}
|
||||
|
||||
- (id) collectionFromId: (NSString *) theCollectionId
|
||||
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
|
||||
|
||||
- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate
|
||||
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
|
||||
|
||||
- (NSException *) dispatchRequest: (id) theRequest
|
||||
inResponse: (id) theResponse
|
||||
context: (id) theContext;
|
||||
|
||||
- (NSURL *) folderTableURL;
|
||||
- (void) ensureFolderTableExists;
|
||||
|
||||
@end
|
||||
@@ -76,6 +76,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <SOGo/NSArray+DAV.h>
|
||||
#import <SOGo/NSDictionary+DAV.h>
|
||||
#import <SOGo/SOGoCache.h>
|
||||
#import <SOGo/SOGoCacheGCSObject.h>
|
||||
#import <SOGo/SOGoDAVAuthenticator.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoMailer.h>
|
||||
@@ -84,6 +86,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <SOGo/SOGoUserFolder.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/GCSSpecialQueries+SOGoCacheObject.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
|
||||
#import <Appointments/SOGoAppointmentFolder.h>
|
||||
#import <Appointments/SOGoAppointmentFolders.h>
|
||||
@@ -114,24 +118,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "SOGoActiveSyncConstants.h"
|
||||
#include "SOGoMailObject+ActiveSync.h"
|
||||
|
||||
#import <GDLContentStore/GCSChannelManager.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
@implementation SOGoActiveSyncDispatcher
|
||||
|
||||
- (id) init
|
||||
{
|
||||
[super init];
|
||||
|
||||
folderTableURL = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(folderTableURL);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) _setFolderSyncKey: (NSString *) theSyncKey
|
||||
{
|
||||
NSMutableDictionary *metadata;
|
||||
|
||||
metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]];
|
||||
|
||||
[metadata setObject: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"SyncKey"] forKey: @"FolderSync"];
|
||||
SOGoCacheGCSObject *o;
|
||||
|
||||
[[[context activeUser] userSettings] setMicrosoftActiveSyncMetadata: metadata
|
||||
forDevice: [context objectForKey: @"DeviceId"]];
|
||||
|
||||
[[[context activeUser] userSettings] synchronize];
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
[[o properties] removeAllObjects];
|
||||
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) _globalMetadataForDevice
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
return [o properties];
|
||||
}
|
||||
|
||||
- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate
|
||||
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
||||
{
|
||||
if (theFolderType == ActiveSyncMailFolder)
|
||||
{
|
||||
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 theIdToTranslate;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -165,7 +221,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
|
||||
collection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", theCollectionId]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
@@ -211,20 +267,52 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", [displayName stringByEncodingImap4FolderName]]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
// If the parrent is 0 -> ok ; otherwise need to build the foldername based on parentId + displayName
|
||||
if ([parentId isEqualToString: @"0"])
|
||||
newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", [displayName stringByEncodingImap4FolderName]]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
else
|
||||
{
|
||||
parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: ActiveSyncMailFolder];
|
||||
newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@/%@", [parentId stringByEncodingImap4FolderName],
|
||||
[displayName stringByEncodingImap4FolderName]]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// handle exists (status == 2)
|
||||
// handle right synckey
|
||||
if ([newFolder create])
|
||||
{
|
||||
SOGoMailAccount *accountFolder;
|
||||
NSDictionary *imapGUIDs;
|
||||
SOGoCacheGCSObject *o;
|
||||
NSString *key;
|
||||
|
||||
nameInContainer = [newFolder nameInContainer];
|
||||
|
||||
// We strip the "folder" prefix
|
||||
nameInContainer = [nameInContainer substringFromIndex: 6];
|
||||
|
||||
// save new guid into cache
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// update GUID in cache
|
||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], nameInContainer ];
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
nameInContainer =[imapGUIDs objectForKey: nameInContainer];
|
||||
|
||||
[[o properties ] setObject: nameInContainer forKey: @"GUID"];
|
||||
[o save];
|
||||
|
||||
nameInContainer = [[NSString stringWithFormat: @"mail/%@", nameInContainer] stringByEscapingURL];
|
||||
}
|
||||
else
|
||||
@@ -305,9 +393,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *serverId;
|
||||
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
|
||||
|
||||
serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
|
||||
serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType];
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
@@ -368,6 +456,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
int status;
|
||||
|
||||
serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
|
||||
serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType];
|
||||
parentId = [[(id)[theDocumentElement getElementsByTagName: @"ParentId"] lastObject] textValue];
|
||||
displayName = [[(id)[theDocumentElement getElementsByTagName: @"DisplayName"] lastObject] textValue];
|
||||
|
||||
@@ -379,7 +468,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
error = [folderToUpdate renameTo: displayName];
|
||||
// If parent is 0 or displayname is not changed it is either a rename of a folder in 0 or a move to 0
|
||||
if ([parentId isEqualToString: @"0"] ||
|
||||
([serverId hasSuffix: [NSString stringWithFormat: @"/%@", displayName]] && [parentId isEqualToString: @"0"]))
|
||||
{
|
||||
error = [folderToUpdate renameTo: [NSString stringWithFormat: @"/%@", [displayName stringByEncodingImap4FolderName]]];
|
||||
}
|
||||
else
|
||||
{
|
||||
parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: folderType];
|
||||
error = [folderToUpdate renameTo: [NSString stringWithFormat: @"%@/%@", [parentId stringByEncodingImap4FolderName],
|
||||
[displayName stringByEncodingImap4FolderName]]];
|
||||
}
|
||||
|
||||
// Handle new name exist
|
||||
if (!error)
|
||||
@@ -395,10 +495,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// See http://msdn.microsoft.com/en-us/library/gg675615(v=exchg.80).aspx
|
||||
// we return '9' - we force a FolderSync
|
||||
status = 9;
|
||||
status = 1;
|
||||
|
||||
[self _setFolderSyncKey: syncKey];
|
||||
|
||||
// FIXME - TODO: We *MUST* update the path in our cache! See -changePathTo in SOGoCacheGCSObject
|
||||
|
||||
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/\">"];
|
||||
@@ -429,15 +531,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
|
||||
NSMutableDictionary *metadata;
|
||||
NSMutableString *s;
|
||||
NSString *syncKey;
|
||||
NSData *d;
|
||||
|
||||
|
||||
BOOL first_sync;
|
||||
int status;
|
||||
|
||||
metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]];
|
||||
metadata = [self _globalMetadataForDevice];
|
||||
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
s = [NSMutableString string];
|
||||
|
||||
@@ -449,7 +552,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
first_sync = YES;
|
||||
syncKey = @"1";
|
||||
}
|
||||
else if (![syncKey isEqualToString: [[metadata objectForKey: @"FolderSync"] objectForKey: @"SyncKey"]])
|
||||
else if (![syncKey isEqualToString: [metadata objectForKey: @"FolderSyncKey"]])
|
||||
{
|
||||
// Synchronization key mismatch or invalid synchronization key
|
||||
status = 9;
|
||||
@@ -460,84 +563,213 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendFormat: @"<FolderSync xmlns=\"FolderHierarchy:\"><Status>%d</Status><SyncKey>%@</SyncKey><Changes>", status, syncKey];
|
||||
|
||||
// Initial sync, let's return the complete folder list
|
||||
if (first_sync)
|
||||
|
||||
if (status == 1)
|
||||
{
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
id currentFolder;
|
||||
|
||||
NSDictionary *folderMetadata;
|
||||
NSArray *allFoldersMetadata;
|
||||
NSString *name, *serverId, *parentId;
|
||||
NSString *key, *cKey, *nkey, *name, *serverId, *parentId;
|
||||
NSDictionary *folderMetadata, *imapGUIDs;
|
||||
NSArray *allFoldersMetadata, *allKeys;
|
||||
NSMutableDictionary *cachedGUIDs;
|
||||
NSMutableString *commands;
|
||||
SOGoCacheGCSObject *o;
|
||||
|
||||
int i, type, command_count;
|
||||
|
||||
int i, type;
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
allFoldersMetadata = [accountFolder allFoldersMetadata];
|
||||
|
||||
// Get GUIDs of folder (IMAP)
|
||||
// e.g. {INBOX = "sogo_73c_192bd57b_d8"
|
||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
||||
|
||||
// See 2.2.3.170.3 Type (FolderSync) - http://msdn.microsoft.com/en-us/library/gg650877(v=exchg.80).aspx
|
||||
[s appendFormat: @"<Count>%d</Count>", [allFoldersMetadata count]+3];
|
||||
cachedGUIDs = [NSMutableDictionary dictionary];
|
||||
|
||||
// No need to read cached folder infos during first sync. Otherwise, pull it from the database.
|
||||
// e.g. {"sogo_73c_192bd57b_d8" = INBOX} - guid = foldername for easy reverse lookup with imapGUIDs
|
||||
if (!first_sync)
|
||||
{
|
||||
NSArray *foldersInCache;
|
||||
NSString *folderName;
|
||||
|
||||
// get the list of folder stored in cache
|
||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], @"0"];
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
foldersInCache = [o folderList: [context objectForKey: @"DeviceId"] newerThanVersion: -1];
|
||||
|
||||
// get guids of folders stored in cache
|
||||
for (i = 0; i < [foldersInCache count]; i++)
|
||||
{
|
||||
folderName = [foldersInCache objectAtIndex: i];
|
||||
key = [folderName substringFromIndex: 1];
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
if ([[o properties ] objectForKey: @"GUID"])
|
||||
[cachedGUIDs setObject: [key substringFromIndex: [key rangeOfString: @"+"].location+7]
|
||||
forKey: [[o properties] objectForKey: @"GUID"]];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle folders that have been deleted over IMAP
|
||||
command_count = 0;
|
||||
commands = [NSMutableString string];
|
||||
allKeys = [cachedGUIDs allKeys];
|
||||
|
||||
for (i = 0; i < [allKeys count]; i++)
|
||||
{
|
||||
cKey = [allKeys objectAtIndex: i];
|
||||
|
||||
if (![imapGUIDs allKeysForObject: cKey])
|
||||
{
|
||||
// Delete folders cache content to avoid stale data if a new folder gets created with the same name
|
||||
key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey: cKey]];
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
// Only send a delete command if GUID is found
|
||||
if ([[o properties] objectForKey: @"GUID"])
|
||||
{
|
||||
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [[NSString stringWithFormat: @"mail/%@", [[o properties ] objectForKey: @"GUID"]] stringByEscapingURL] ];
|
||||
command_count++;
|
||||
}
|
||||
|
||||
[[o properties] removeAllObjects];
|
||||
[o save];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle addition and changes
|
||||
for (i = 0; i < [allFoldersMetadata count]; i++)
|
||||
{
|
||||
folderMetadata = [allFoldersMetadata objectAtIndex: i];
|
||||
serverId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"path"]];
|
||||
|
||||
// No GUID -> no sync
|
||||
if (!([imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]))
|
||||
continue;
|
||||
|
||||
serverId = [NSString stringWithFormat: @"mail/%@", [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]];
|
||||
name = [folderMetadata objectForKey: @"displayName"];
|
||||
|
||||
if ([name hasPrefix: @"/"])
|
||||
name = [name substringFromIndex: 1];
|
||||
|
||||
if ([name hasSuffix: @"/"])
|
||||
name = [name substringToIndex: [name length]-2];
|
||||
|
||||
name = [name substringToIndex: [name length]-1];
|
||||
|
||||
type = [[folderMetadata objectForKey: @"type"] activeSyncFolderType];
|
||||
|
||||
parentId = @"0";
|
||||
|
||||
|
||||
if ([folderMetadata objectForKey: @"parent"])
|
||||
{
|
||||
parentId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"parent"]];
|
||||
parentId = [NSString stringWithFormat: @"mail/%@", [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"parent"] substringFromIndex: 1]]];
|
||||
name = [[name pathComponents] lastObject];
|
||||
}
|
||||
|
||||
[s appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>",
|
||||
[serverId stringByEscapingURL],
|
||||
[parentId stringByEscapingURL],
|
||||
type,
|
||||
[name activeSyncRepresentationInContext: context]];
|
||||
|
||||
// Decide between add and change
|
||||
if ([cachedGUIDs objectForKey: [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]])
|
||||
{
|
||||
// Search GUID to check name change in cache (diff between IMAP and cache)
|
||||
if ((![[[folderMetadata objectForKey: @"path"] substringFromIndex: 1] isEqualToString: [imapGUIDs objectForKey: [cachedGUIDs objectForKey:
|
||||
[[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]]))
|
||||
{
|
||||
key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey:
|
||||
[imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]];
|
||||
nkey = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[folderMetadata objectForKey: @"path"] substringFromIndex: 1] ];
|
||||
|
||||
if (![key isEqualToString: nkey])
|
||||
{
|
||||
[commands appendFormat: @"<Update><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Update>",
|
||||
[serverId stringByEscapingURL],
|
||||
[parentId stringByEscapingURL],
|
||||
type,
|
||||
[name activeSyncRepresentationInContext: context]];
|
||||
|
||||
|
||||
// Change path in cache
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
[o changePathTo: [NSString stringWithFormat: @"/%@", nkey]]; // ?? why is '/' prefix needed - problem in changePathTo?
|
||||
|
||||
command_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[commands appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>",
|
||||
[serverId stringByEscapingURL],
|
||||
[parentId stringByEscapingURL],
|
||||
type,
|
||||
[name activeSyncRepresentationInContext: context]];
|
||||
|
||||
// Store folder's GUID in cache
|
||||
key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]];
|
||||
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
[[o properties ] setObject: [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]] forKey: @"GUID"];
|
||||
[o save];
|
||||
|
||||
command_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// We add the personal calendar - events
|
||||
// FIXME: add all calendars
|
||||
currentFolder = [[context activeUser] personalCalendarFolderInContext: context];
|
||||
name = [NSString stringWithFormat: @"vevent/%@", [currentFolder nameInContainer]];
|
||||
[s appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>", name, @"0", 8, [[currentFolder displayName] activeSyncRepresentationInContext: context]];
|
||||
|
||||
// We add the personal calendar - tasks
|
||||
// FIXME: add all calendars
|
||||
currentFolder = [[context activeUser] personalCalendarFolderInContext: context];
|
||||
name = [NSString stringWithFormat: @"vtodo/%@", [currentFolder nameInContainer]];
|
||||
[s appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>", name, @"0", 7, [[currentFolder displayName] activeSyncRepresentationInContext: context]];
|
||||
|
||||
// We add the personal address book
|
||||
// FIXME: add all address books
|
||||
currentFolder = [[context activeUser] personalContactsFolderInContext: context];
|
||||
name = [NSString stringWithFormat: @"vcard/%@", [currentFolder nameInContainer]];
|
||||
[s appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>", name, @"0", 9, [[currentFolder displayName] activeSyncRepresentationInContext: context]];
|
||||
if (first_sync)
|
||||
[s appendFormat: @"<Count>%d</Count>", command_count+3];
|
||||
else
|
||||
[s appendFormat: @"<Count>%d</Count>", command_count];
|
||||
|
||||
if (command_count > 0)
|
||||
[s appendFormat: @"%@", commands];
|
||||
|
||||
if (first_sync)
|
||||
{
|
||||
// We add the personal calendar - events
|
||||
// FIXME: add all calendars
|
||||
currentFolder = [[context activeUser] personalCalendarFolderInContext: context];
|
||||
name = [NSString stringWithFormat: @"vevent/%@", [currentFolder nameInContainer]];
|
||||
[s appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>", name, @"0", 8, [[currentFolder displayName] activeSyncRepresentationInContext: context]];
|
||||
|
||||
// We add the personal calendar - tasks
|
||||
// FIXME: add all calendars
|
||||
currentFolder = [[context activeUser] personalCalendarFolderInContext: context];
|
||||
name = [NSString stringWithFormat: @"vtodo/%@", [currentFolder nameInContainer]];
|
||||
[s appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>", name, @"0", 7, [[currentFolder displayName] activeSyncRepresentationInContext: context]];
|
||||
|
||||
// We add the personal address book
|
||||
// FIXME: add all address books
|
||||
currentFolder = [[context activeUser] personalContactsFolderInContext: context];
|
||||
name = [NSString stringWithFormat: @"vcard/%@", [currentFolder nameInContainer]];
|
||||
[s appendFormat: @"<Add><ServerId>%@</ServerId><ParentId>%@</ParentId><Type>%d</Type><DisplayName>%@</DisplayName></Add>", name, @"0", 9, [[currentFolder displayName] activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[s appendString: @"</Changes></FolderSync>"];
|
||||
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// From: http://msdn.microsoft.com/en-us/library/ee157980(v=exchg.80).aspx :
|
||||
@@ -584,6 +816,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue];
|
||||
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
|
||||
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
||||
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
||||
|
||||
//
|
||||
@@ -658,6 +891,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
fileReference = [[[(id)[theDocumentElement getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL];
|
||||
|
||||
realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType];
|
||||
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
||||
|
||||
if (folderType == ActiveSyncMailFolder)
|
||||
{
|
||||
@@ -751,6 +985,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
status = 1;
|
||||
|
||||
realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
|
||||
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
||||
userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue];
|
||||
requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue];
|
||||
appointmentObject = nil;
|
||||
@@ -867,87 +1102,119 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
NSString *srcMessageId, *srcFolderId, *dstFolderId, *dstMessageId;
|
||||
SOGoMicrosoftActiveSyncFolderType srcFolderType, dstFolderType;
|
||||
id <DOMElement> aMoveOperation;
|
||||
NSArray *moveOperations;
|
||||
NSMutableString *s;
|
||||
NSData *d;
|
||||
int i;
|
||||
|
||||
srcMessageId = [[(id)[theDocumentElement getElementsByTagName: @"SrcMsgId"] lastObject] textValue];
|
||||
srcFolderId = [[[(id)[theDocumentElement getElementsByTagName: @"SrcFldId"] lastObject] textValue] realCollectionIdWithFolderType: &srcFolderType];
|
||||
dstFolderId = [[[(id)[theDocumentElement getElementsByTagName: @"DstFldId"] lastObject] textValue] realCollectionIdWithFolderType: &dstFolderType];
|
||||
moveOperations = (id)[theDocumentElement getElementsByTagName: @"Move"];
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
// FIXME
|
||||
if (srcFolderType == ActiveSyncMailFolder && dstFolderType == ActiveSyncMailFolder)
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<MoveItems xmlns=\"Move:\">"];
|
||||
|
||||
for (i = 0; i < [moveOperations count]; i++)
|
||||
{
|
||||
NGImap4Client *client;
|
||||
id currentCollection;
|
||||
|
||||
NSDictionary *response;
|
||||
NSString *v;
|
||||
|
||||
// userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
// accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
// currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
aMoveOperation = [moveOperations objectAtIndex: i];
|
||||
|
||||
currentCollection = [self collectionFromId: srcFolderId type: srcFolderType];
|
||||
srcMessageId = [[(id)[aMoveOperation getElementsByTagName: @"SrcMsgId"] lastObject] textValue];
|
||||
srcFolderId = [[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] realCollectionIdWithFolderType: &srcFolderType];
|
||||
dstFolderId = [[[(id)[aMoveOperation getElementsByTagName: @"DstFldId"] lastObject] textValue] realCollectionIdWithFolderType: &dstFolderType];
|
||||
|
||||
[s appendString: @"<Response>"];
|
||||
|
||||
// [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", srcFolderId]
|
||||
// inContext: context
|
||||
// acquire: NO];
|
||||
|
||||
client = [[currentCollection imap4Connection] client];
|
||||
[client select: srcFolderId];
|
||||
response = [client copyUid: [srcMessageId intValue]
|
||||
toFolder: [NSString stringWithFormat: @"/%@", dstFolderId]];
|
||||
|
||||
// We extract the destionation message id
|
||||
dstMessageId = nil;
|
||||
|
||||
if ([[response objectForKey: @"result"] boolValue]
|
||||
&& (v = [[[response objectForKey: @"RawResponse"] objectForKey: @"ResponseResult"] objectForKey: @"flag"])
|
||||
&& [v hasPrefix: @"COPYUID "])
|
||||
// FIXME - we should support moving events between calendars, for example, or
|
||||
// or contacts between address books.
|
||||
if (srcFolderType == ActiveSyncMailFolder && dstFolderType == ActiveSyncMailFolder)
|
||||
{
|
||||
dstMessageId = [[v componentsSeparatedByString: @" "] lastObject];
|
||||
NGImap4Client *client;
|
||||
id currentCollection;
|
||||
|
||||
NSDictionary *response;
|
||||
NSString *v;
|
||||
|
||||
srcFolderId = [self globallyUniqueIDToIMAPFolderName: srcFolderId type: srcFolderType];
|
||||
dstFolderId = [self globallyUniqueIDToIMAPFolderName: dstFolderId type: dstFolderType];
|
||||
|
||||
// We mark the original message as deleted
|
||||
response = [client storeFlags: [NSArray arrayWithObject: @"Deleted"]
|
||||
forUIDs: [NSArray arrayWithObject: srcMessageId]
|
||||
addOrRemove: YES];
|
||||
currentCollection = [self collectionFromId: srcFolderId type: srcFolderType];
|
||||
|
||||
client = [[currentCollection imap4Connection] client];
|
||||
[client select: srcFolderId];
|
||||
response = [client copyUid: [srcMessageId intValue]
|
||||
toFolder: [NSString stringWithFormat: @"/%@", dstFolderId]];
|
||||
|
||||
// We extract the destionation message id
|
||||
dstMessageId = nil;
|
||||
|
||||
if ([[response objectForKey: @"result"] boolValue]
|
||||
&& (v = [[[response objectForKey: @"RawResponse"] objectForKey: @"ResponseResult"] objectForKey: @"flag"])
|
||||
&& [v hasPrefix: @"COPYUID "])
|
||||
{
|
||||
dstMessageId = [[v componentsSeparatedByString: @" "] lastObject];
|
||||
|
||||
// We mark the original message as deleted
|
||||
response = [client storeFlags: [NSArray arrayWithObject: @"Deleted"]
|
||||
forUIDs: [NSArray arrayWithObject: srcMessageId]
|
||||
addOrRemove: YES];
|
||||
|
||||
if ([[response valueForKey: @"result"] boolValue])
|
||||
[(SOGoMailFolder *)currentCollection expunge];
|
||||
|
||||
}
|
||||
|
||||
if (!dstMessageId)
|
||||
{
|
||||
// FIXME: should we return 1 or 2 here?
|
||||
[s appendFormat: @"<Status>%d</Status>", 2];
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// If the MoveItems operation is initiated by an Outlook client, we save the "deviceType+dstMessageId" to use it later in order to
|
||||
// modify the Sync command from "add" to "change" (see SOGoActiveSyncDispatcher+Sync.m: -processSyncGetChanges: ...).
|
||||
// This is to avoid Outlook creating dupes when moving messages across folfers.
|
||||
//
|
||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"])
|
||||
{
|
||||
NSString *key;
|
||||
|
||||
// The key must be pretty verbose. We use the <uid>+<DeviceType>+<target folder>+<DstMsgId>
|
||||
key = [NSString stringWithFormat: @"%@+%@+%@+%@",
|
||||
[[context activeUser] login],
|
||||
[context objectForKey: @"DeviceType"],
|
||||
dstFolderId,
|
||||
dstMessageId];
|
||||
|
||||
|
||||
[[SOGoCache sharedCache] setValue: @"MovedItem"
|
||||
forKey: key];
|
||||
}
|
||||
|
||||
// Everything is alright, lets return the proper response. "Status == 3" means success.
|
||||
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
||||
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", dstMessageId];
|
||||
[s appendFormat: @"<Status>%d</Status>", 3];
|
||||
}
|
||||
|
||||
if ([[response valueForKey: @"result"] boolValue])
|
||||
[(SOGoMailFolder *)currentCollection expunge];
|
||||
|
||||
}
|
||||
|
||||
if (!dstMessageId)
|
||||
{
|
||||
[theResponse setStatus: 500];
|
||||
[theResponse appendContentString: @"Unable to move message"];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableString *s;
|
||||
NSData *d;
|
||||
|
||||
// Everything is alright, lets return the proper response. "Status == 3" means success.
|
||||
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: @"<MoveItems xmlns=\"Move:\">"];
|
||||
[s appendString: @"<Response>"];
|
||||
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
|
||||
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", dstMessageId];
|
||||
[s appendFormat: @"<Status>%d</Status>", 3];
|
||||
[s appendString: @"</Response>"];
|
||||
[s appendString: @"</MoveItems>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
[theResponse setContent: d];
|
||||
// Non-mail move operations - unsupported for now.
|
||||
[s appendFormat: @"<Status>%d</Status>", 1];
|
||||
}
|
||||
|
||||
[s appendString: @"</Response>"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[theResponse setStatus: 500];
|
||||
[theResponse appendContentString: @"Unsupported move operation"];
|
||||
}
|
||||
|
||||
|
||||
[s appendString: @"</MoveItems>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1007,6 +1274,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
|
||||
//
|
||||
// We ignore everything for now.
|
||||
//
|
||||
- (void) processProvision: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSMutableString *s;
|
||||
NSData *d;
|
||||
|
||||
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: @"<Provision xmlns=\"Provision:\">"];
|
||||
[s appendString: @"</Provision>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
|
||||
//
|
||||
// <?xml version="1.0"?>
|
||||
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
||||
@@ -1390,11 +1677,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
NSString *folderId, *itemId, *realCollectionId;
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
id value;
|
||||
|
||||
folderId = [[(id)[theDocumentElement getElementsByTagName: @"FolderId"] lastObject] textValue];
|
||||
itemId = [[(id)[theDocumentElement getElementsByTagName: @"ItemId"] lastObject] textValue];
|
||||
realCollectionId = [folderId realCollectionIdWithFolderType: &folderType];
|
||||
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
||||
|
||||
value = [theDocumentElement getElementsByTagName: @"ReplaceMime"];
|
||||
|
||||
// ReplaceMime isn't specified so we must NOT use the server copy
|
||||
// but rather take the data as-is from the client.
|
||||
if (![value count])
|
||||
{
|
||||
[self processSendMail: theDocumentElement
|
||||
inResponse: theResponse];
|
||||
return;
|
||||
}
|
||||
|
||||
if (folderType == ActiveSyncMailFolder)
|
||||
{
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
@@ -1522,19 +1822,53 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
ASSIGN(context, theContext);
|
||||
|
||||
// Get the device ID and "stash" it
|
||||
// Get the device ID, device type and "stash" them
|
||||
deviceId = [[theRequest uri] deviceId];
|
||||
[context setObject: deviceId forKey: @"DeviceId"];
|
||||
[context setObject: [[theRequest uri] deviceType] forKey: @"DeviceType"];
|
||||
|
||||
cmdName = [[theRequest uri] command];
|
||||
|
||||
// We make sure our cache table exists
|
||||
[self ensureFolderTableExists];
|
||||
|
||||
//
|
||||
// If the MS-ASProtocolVersion header is set to "12.1", the body of the SendMail request is
|
||||
// is a "message/rfc822" payload - otherwise, it's a WBXML blob.
|
||||
//
|
||||
if ([cmdName caseInsensitiveCompare: @"SendMail"] == NSOrderedSame &&
|
||||
[[theRequest headerForKey: @"content-type"] caseInsensitiveCompare: @"message/rfc822"] == NSOrderedSame)
|
||||
{
|
||||
NSString *s, *xml;
|
||||
|
||||
if ([[theRequest contentAsString] rangeOfString: @"Date: "
|
||||
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]];
|
||||
}
|
||||
else
|
||||
{
|
||||
s = [theRequest contentAsString];
|
||||
}
|
||||
|
||||
xml = [NSString stringWithFormat: @"<?xml version=\"1.0\"?><!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\"><SendMail xmlns=\"ComposeMail:\"><SaveInSentItems/><MIME>%@</MIME></SendMail>", [s stringByEncodingBase64]];
|
||||
|
||||
d = [xml dataUsingEncoding: NSASCIIStringEncoding];
|
||||
}
|
||||
else
|
||||
{
|
||||
d = [[theRequest content] wbxml2xml];
|
||||
}
|
||||
|
||||
d = [[theRequest content] wbxml2xml];
|
||||
documentElement = nil;
|
||||
|
||||
if (!d)
|
||||
{
|
||||
// We check if it's a Ping command with no body.
|
||||
// See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details
|
||||
cmdName = [[theRequest uri] command];
|
||||
|
||||
// See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details
|
||||
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame)
|
||||
return [NSException exceptionWithHTTPStatus: 500];
|
||||
}
|
||||
@@ -1561,7 +1895,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
[theResponse setHeader: @"application/vnd.ms-sync.wbxml" forKey: @"Content-Type"];
|
||||
[theResponse setHeader: @"14.1" forKey: @"MS-Server-ActiveSync"];
|
||||
[theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
|
||||
[theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
|
||||
[theResponse setHeader: @"2.0,2.1,2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
|
||||
|
||||
RELEASE(context);
|
||||
@@ -1569,4 +1903,72 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSURL *) folderTableURL
|
||||
{
|
||||
NSString *urlString, *ocFSTableName;
|
||||
NSMutableArray *parts;
|
||||
SOGoUser *user;
|
||||
|
||||
if (!folderTableURL)
|
||||
{
|
||||
user = [context activeUser];
|
||||
|
||||
if (![user loginInDomain])
|
||||
return nil;
|
||||
|
||||
urlString = [[user domainDefaults] folderInfoURL];
|
||||
parts = [[urlString componentsSeparatedByString: @"/"]
|
||||
mutableCopy];
|
||||
[parts autorelease];
|
||||
if ([parts count] == 5)
|
||||
{
|
||||
/* If "OCSFolderInfoURL" is properly configured, we must have 5
|
||||
parts in this url. */
|
||||
ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@",
|
||||
[[user loginInDomain] asCSSIdentifier]];
|
||||
[parts replaceObjectAtIndex: 4 withObject: ocFSTableName];
|
||||
folderTableURL
|
||||
= [NSURL URLWithString: [parts componentsJoinedByString: @"/"]];
|
||||
[folderTableURL retain];
|
||||
}
|
||||
else
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"'OCSFolderInfoURL' is not set"];
|
||||
}
|
||||
|
||||
return folderTableURL;
|
||||
}
|
||||
|
||||
- (void) ensureFolderTableExists
|
||||
{
|
||||
GCSChannelManager *cm;
|
||||
EOAdaptorChannel *channel;
|
||||
NSString *tableName, *query;
|
||||
GCSSpecialQueries *queries;
|
||||
|
||||
[self folderTableURL];
|
||||
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
channel = [cm acquireOpenChannelForURL: folderTableURL];
|
||||
|
||||
/* FIXME: make use of [EOChannelAdaptor describeTableNames] instead */
|
||||
tableName = [[folderTableURL path] lastPathComponent];
|
||||
if (tableName &&
|
||||
[channel evaluateExpressionX:
|
||||
[NSString stringWithFormat: @"SELECT count(*) FROM %@",
|
||||
tableName]])
|
||||
{
|
||||
queries = [channel specialQueries];
|
||||
query = [queries createSOGoCacheGCSFolderTableWithName: tableName];
|
||||
if ([channel evaluateExpressionX: query])
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"could not create special table '%@'", tableName];
|
||||
}
|
||||
else
|
||||
[channel cancelFetch];
|
||||
|
||||
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "SOGoMailObject+ActiveSync.h"
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSException.h>
|
||||
@@ -42,12 +43,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
#import <NGExtensions/NGQuotedPrintableCoding.h>
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
#import <NGExtensions/NSString+Encoding.h>
|
||||
#import <NGImap4/NGImap4Envelope.h>
|
||||
#import <NGImap4/NGImap4EnvelopeAddress.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeFileData.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
#import <NGMime/NGMimeType.h>
|
||||
#import <NGMail/NGMimeMessageParser.h>
|
||||
#import <NGMail/NGMimeMessage.h>
|
||||
#import <NGMail/NGMimeMessageGenerator.h>
|
||||
|
||||
#include "iCalTimeZone+ActiveSync.h"
|
||||
#include "NSData+ActiveSync.h"
|
||||
#include "NSDate+ActiveSync.h"
|
||||
@@ -166,8 +176,8 @@ struct GlobalObjectId {
|
||||
- (NSString *) _emailAddressesFrom: (NSArray *) enveloppeAddresses
|
||||
{
|
||||
NGImap4EnvelopeAddress *address;
|
||||
NSString *email, *rc, *name;
|
||||
NSMutableArray *addresses;
|
||||
NSString *email, *rc;
|
||||
int i, max;
|
||||
|
||||
rc = nil;
|
||||
@@ -179,7 +189,8 @@ struct GlobalObjectId {
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
address = [enveloppeAddresses objectAtIndex: i];
|
||||
email = [NSString stringWithFormat: @"\"%@\" <%@>", [address personalName], [address baseEMail]];
|
||||
name = [address personalName];
|
||||
email = [NSString stringWithFormat: @"\"%@\" <%@>", (name ? name : [address baseEMail]), [address baseEMail]];
|
||||
|
||||
if (email)
|
||||
[addresses addObject: email];
|
||||
@@ -195,7 +206,7 @@ struct GlobalObjectId {
|
||||
//
|
||||
- (NSData *) _preferredBodyDataInMultipartUsingType: (int) theType
|
||||
{
|
||||
NSString *key, *plainKey, *htmlKey, *type, *subtype;
|
||||
NSString *encoding, *key, *plainKey, *htmlKey, *type, *subtype;
|
||||
NSDictionary *textParts, *part;
|
||||
NSEnumerator *e;
|
||||
NSData *d;
|
||||
@@ -218,18 +229,135 @@ struct GlobalObjectId {
|
||||
plainKey = key;
|
||||
}
|
||||
|
||||
key = nil;
|
||||
|
||||
if (theType == 2)
|
||||
{
|
||||
d = [[self fetchPlainTextParts] objectForKey: htmlKey];
|
||||
}
|
||||
key = htmlKey;
|
||||
else if (theType == 1)
|
||||
key = plainKey;
|
||||
|
||||
if (key)
|
||||
{
|
||||
d = [[self fetchPlainTextParts] objectForKey: plainKey];
|
||||
d = [[self fetchPlainTextParts] objectForKey: key];
|
||||
|
||||
encoding = [[self lookupInfoForBodyPart: key] objectForKey: @"encoding"];
|
||||
|
||||
if ([encoding caseInsensitiveCompare: @"base64"] == NSOrderedSame)
|
||||
d = [d dataByDecodingBase64];
|
||||
else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame)
|
||||
d = [d dataByDecodingQuotedPrintableTransferEncoding];
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (void) _sanitizedMIMEPart: (id) thePart
|
||||
performed: (BOOL *) b
|
||||
{
|
||||
if ([thePart isKindOfClass: [NGMimeMultipartBody class]])
|
||||
{
|
||||
NGMimeBodyPart *part;
|
||||
NSArray *parts;
|
||||
int i;
|
||||
|
||||
parts = [thePart parts];
|
||||
|
||||
for (i = 0; i < [parts count]; i++)
|
||||
{
|
||||
part = [parts objectAtIndex: i];
|
||||
|
||||
[self _sanitizedMIMEPart: part
|
||||
performed: b];
|
||||
}
|
||||
}
|
||||
else if ([thePart isKindOfClass: [NGMimeBodyPart class]])
|
||||
{
|
||||
NGMimeFileData *fdata;
|
||||
id body;
|
||||
|
||||
body = [thePart body];
|
||||
|
||||
if ([body isKindOfClass: [NGMimeMultipartBody class]])
|
||||
{
|
||||
[self _sanitizedMIMEPart: body
|
||||
performed: b];
|
||||
}
|
||||
else if ([body isKindOfClass: [NSData class]] &&
|
||||
[[[thePart contentType] type] isEqualToString: @"text"] &&
|
||||
([[[thePart contentType] subType] isEqualToString: @"plain"] || [[[thePart contentType] subType] isEqualToString: @"html"]))
|
||||
{
|
||||
// We make sure everything is encoded in UTF-8
|
||||
NSString *charset, *s;
|
||||
NGMimeType *mimeType;
|
||||
int encoding;
|
||||
|
||||
charset = [[thePart contentType] valueOfParameter: @"charset"];
|
||||
encoding = [NGMimeType stringEncodingForCharset: charset];
|
||||
|
||||
s = [[NSString alloc] initWithData: body encoding: encoding];
|
||||
AUTORELEASE(s);
|
||||
|
||||
if (s)
|
||||
{
|
||||
body = [s dataUsingEncoding: NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
mimeType = [NGMimeType mimeType: [[thePart contentType] type]
|
||||
subType: [[thePart contentType] subType]
|
||||
parameters: [NSDictionary dictionaryWithObject: @"utf-8" forKey: @"charset"]];
|
||||
[thePart setHeader: mimeType forKey: @"content-type"];
|
||||
|
||||
fdata = [[NGMimeFileData alloc] initWithBytes: [body bytes]
|
||||
length: [body length]];
|
||||
|
||||
[thePart setBody: fdata];
|
||||
RELEASE(fdata);
|
||||
*b = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSData *) _sanitizedMIMEMessage
|
||||
{
|
||||
NGMimeMessageParser *parser;
|
||||
NGMimeMessage *message;
|
||||
NSData *d;
|
||||
|
||||
BOOL b;
|
||||
|
||||
d = [self content];
|
||||
|
||||
parser = [[NGMimeMessageParser alloc] init];
|
||||
AUTORELEASE(parser);
|
||||
|
||||
message = [parser parsePartFromData: d];
|
||||
b = NO;
|
||||
|
||||
if (message)
|
||||
{
|
||||
[self _sanitizedMIMEPart: [message body]
|
||||
performed: &b];
|
||||
|
||||
if (b)
|
||||
{
|
||||
NGMimeMessageGenerator *generator;
|
||||
|
||||
generator = [[NGMimeMessageGenerator alloc] init];
|
||||
AUTORELEASE(generator);
|
||||
|
||||
d = [generator generateMimeFromPart: message];
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -265,6 +393,8 @@ struct GlobalObjectId {
|
||||
|
||||
if ([encoding caseInsensitiveCompare: @"base64"] == NSOrderedSame)
|
||||
d = [d dataByDecodingBase64];
|
||||
else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame)
|
||||
d = [d dataByDecodingQuotedPrintableTransferEncoding];
|
||||
|
||||
// Check if we must convert html->plain
|
||||
if (theType == 1 && [subtype isEqualToString: @"html"])
|
||||
@@ -285,7 +415,12 @@ struct GlobalObjectId {
|
||||
}
|
||||
else if (theType == 4)
|
||||
{
|
||||
d = [self content];
|
||||
// We sanitize the content *ONLY* for Outlook clients. Outlook has strange issues
|
||||
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
|
||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"])
|
||||
d = [self _sanitizedMIMEMessage];
|
||||
else
|
||||
d = [self content];
|
||||
}
|
||||
|
||||
return d;
|
||||
@@ -345,7 +480,9 @@ struct GlobalObjectId {
|
||||
//
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) _context
|
||||
{
|
||||
NSAutoreleasePool *pool;
|
||||
NSData *d, *globalObjId;
|
||||
NSArray *attachmentKeys;
|
||||
NSMutableString *s;
|
||||
id value;
|
||||
|
||||
@@ -377,7 +514,7 @@ struct GlobalObjectId {
|
||||
// DateReceived
|
||||
value = [self date];
|
||||
if (value)
|
||||
[s appendFormat: @"<DateReceived xmlns=\"Email:\">%@</DateReceived>", [value activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
[s appendFormat: @"<DateReceived xmlns=\"Email:\">%@</DateReceived>", [value activeSyncRepresentationInContext: context]];
|
||||
|
||||
// DisplayTo
|
||||
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">%@</DisplayTo>", [[context activeUser] login]];
|
||||
@@ -530,11 +667,16 @@ struct GlobalObjectId {
|
||||
// [s appendFormat: @"<Reply-To xmlns=\"Email:\">%@</Reply-To>", [addressFormatter stringForArray: replyTo]];
|
||||
|
||||
// InternetCPID - 65001 == UTF-8, we use this all the time for now.
|
||||
// - 20127 == US-ASCII
|
||||
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
|
||||
|
||||
// Body - namespace 17
|
||||
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
||||
|
||||
// Make use of a local pool here as _preferredBodyDataUsingType:nativeType: will consume
|
||||
// a significant amout of RAM and file descriptors
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
nativeBodyType = 1;
|
||||
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
|
||||
|
||||
@@ -548,6 +690,10 @@ struct GlobalObjectId {
|
||||
// FIXME: This is a hack. We should normally avoid doing this as we might get
|
||||
// broken encodings. We should rather tell that the data was truncated and expect
|
||||
// a ItemOperations call to download the whole base64 encoding multipart.
|
||||
//
|
||||
// See http://social.msdn.microsoft.com/Forums/en-US/b9944e49-9bc9-4ab8-ba33-a9fc08557c5b/mime-raw-data-in-eas-sync-response?forum=os_exchangeprotocols
|
||||
// for an "interesting" discussion around this.
|
||||
//
|
||||
if (!content)
|
||||
content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding];
|
||||
|
||||
@@ -560,15 +706,21 @@ struct GlobalObjectId {
|
||||
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
|
||||
[s appendFormat: @"<Truncated>%d</Truncated>", 0];
|
||||
[s appendFormat: @"<Truncated>%d</Truncated>", truncated];
|
||||
[s appendFormat: @"<Preview></Preview>"];
|
||||
|
||||
if (!truncated)
|
||||
[s appendFormat: @"<Data>%@</Data>", content];
|
||||
{
|
||||
[s appendFormat: @"<Data>%@</Data>", content];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
|
||||
}
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
|
||||
DESTROY(pool);
|
||||
|
||||
// Attachments -namespace 16
|
||||
NSArray *attachmentKeys = [self fetchFileAttachmentKeys];
|
||||
attachmentKeys = [self fetchFileAttachmentKeys];
|
||||
if ([attachmentKeys count])
|
||||
{
|
||||
int i;
|
||||
@@ -619,6 +771,16 @@ struct GlobalObjectId {
|
||||
//
|
||||
//
|
||||
//
|
||||
// Exemple for a message being marked as read:
|
||||
//
|
||||
// <Change>
|
||||
// <ServerId>607</ServerId>
|
||||
// <ApplicationData>
|
||||
// <Read xmlns="Email:">1</Read>
|
||||
// </ApplicationData>
|
||||
// </Change>
|
||||
// </Commands>
|
||||
//
|
||||
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) _context
|
||||
{
|
||||
@@ -633,6 +795,14 @@ struct GlobalObjectId {
|
||||
else
|
||||
[self removeFlags: @"\\Flagged"];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"Read"]))
|
||||
{
|
||||
if ([o intValue])
|
||||
[self addFlags: @"seen"];
|
||||
else
|
||||
[self removeFlags: @"seen"];;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Inverse inc. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __SOGOSYNCCACHEOBJECT_H__
|
||||
#define __SOGOSYNCCACHEOBJECT_H__
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
|
||||
@class NSNull;
|
||||
|
||||
@interface SOGoSyncCacheObject : NSObject
|
||||
{
|
||||
id _uid;
|
||||
id _sequence;
|
||||
}
|
||||
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
|
||||
- (id) uid;
|
||||
- (void) setUID: (id) theUID;
|
||||
- (id) sequence;
|
||||
- (void) setSequence: (id) theSequence;
|
||||
|
||||
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject;
|
||||
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Inverse inc. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#include "SOGoSyncCacheObject.h"
|
||||
|
||||
#import <Foundation/NSNull.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
@implementation SOGoSyncCacheObject
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
_uid = nil;
|
||||
_sequence = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [[self alloc] init];
|
||||
|
||||
[o setUID: theUID];
|
||||
[o setSequence: theSequence];
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(_uid);
|
||||
RELEASE(_sequence);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id) uid
|
||||
{
|
||||
return _uid;
|
||||
}
|
||||
|
||||
- (void) setUID: (id) theUID
|
||||
{
|
||||
ASSIGN(_uid, theUID);
|
||||
}
|
||||
|
||||
- (id) sequence
|
||||
{
|
||||
return _sequence;
|
||||
}
|
||||
|
||||
- (void) setSequence: (id) theSequence
|
||||
{
|
||||
ASSIGN(_sequence, theSequence);
|
||||
}
|
||||
|
||||
|
||||
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
return [[self uid] compare: [theSyncCacheObject uid]];
|
||||
}
|
||||
|
||||
//
|
||||
// We might get NSNull values here, so if both are NSNull instances,
|
||||
// we sort by UID. If both sequences are equal, we also sort by UID.
|
||||
//
|
||||
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
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]])
|
||||
return NSOrderedDescending;
|
||||
|
||||
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)
|
||||
return [self compareUID: theSyncCacheObject];
|
||||
|
||||
return [[self sequence] compare: [theSyncCacheObject sequence]];
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@-%@", _uid, _sequence];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -62,7 +62,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[response setHeader: @"OPTIONS, POST" forKey: @"Allow"];
|
||||
[response setHeader: @"14.1" forKey: @"MS-Server-ActiveSync"];
|
||||
[response setHeader: @"2.0,2.1,2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
|
||||
[response setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
|
||||
[response setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
|
||||
[response setHeader: @"OPTIONS, POST" forKey: @"Public"];
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# common make file for SoObject bundles
|
||||
|
||||
include ../config.make
|
||||
include $(GNUSTEP_MAKEFILES)/common.make
|
||||
include ../Version
|
||||
@@ -18,10 +16,10 @@ ADDITIONAL_INCLUDE_DIRS += \
|
||||
-I../../SOPE
|
||||
|
||||
ADDITIONAL_LIB_DIRS += \
|
||||
-L../SOGo/SOGo.framework/ \
|
||||
-L../../SOGo/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-L../../OGoContentStore/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-L../../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-L../SoObjects/SOGo/SOGo.framework/ \
|
||||
-L../SoObjects/SOGo/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-L../OGoContentStore/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-L../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-L/usr/local/lib
|
||||
|
||||
BUNDLE_LIBS += \
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Inverse inc. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
#ifndef __ICALALARMACTIVESYNC_H__
|
||||
#define __ICALALARMACTIVESYNC_H__
|
||||
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
|
||||
@class NSString;
|
||||
@class WOContext;
|
||||
|
||||
@interface iCalAlarm (ActiveSync)
|
||||
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context;
|
||||
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) context;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Inverse inc. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
#import "iCalAlarm+ActiveSync.h"
|
||||
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
#import <NGObjWeb/WOContext.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
|
||||
@implementation iCalAlarm (ActiveSync)
|
||||
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
||||
{
|
||||
NSMutableString *s;
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
if ([[self action] caseInsensitiveCompare: @"DISPLAY"] == NSOrderedSame)
|
||||
{
|
||||
NSCalendarDate *nextAlarmDate;
|
||||
NSInteger delta;
|
||||
|
||||
nextAlarmDate = [self nextAlarmDate];
|
||||
delta = (int)(([[(iCalEvent *)parent startDate] timeIntervalSince1970] - [nextAlarmDate timeIntervalSince1970])/60);
|
||||
|
||||
// don't send negative reminder - not supported
|
||||
if (delta > 0)
|
||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta];
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
iCalTrigger *trigger;
|
||||
id o;
|
||||
|
||||
o = [theValues objectForKey: @"Reminder"];
|
||||
|
||||
// Outlook: if reminder is set to 0 minutes before start save it as 1 minute since -> 0 minutes in not accepted by SOGo
|
||||
if ([o isEqualToString: @"0"])
|
||||
o = @"1";
|
||||
|
||||
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
|
||||
[trigger setValueType: @"DURATION"];
|
||||
[self setTrigger: trigger];
|
||||
[self setAction: @"DISPLAY"];
|
||||
|
||||
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
|
||||
// 1week = -P1W
|
||||
// 2weeks > -PxD
|
||||
// xdays > -PxD
|
||||
// xhours -> -PTxH
|
||||
// xmin -> -PTxM
|
||||
if ([o intValue] == 10080)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-P1W" ] forKey: @""];
|
||||
else
|
||||
{
|
||||
if (([o intValue] % 1440) == 0)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-P%dD", ([o intValue] / 1440)] forKey: @""];
|
||||
else
|
||||
{
|
||||
if (([o intValue] % 60) == 0)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%dH", ([o intValue] / 60)] forKey: @""];
|
||||
else
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%@M", o] forKey: @""];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -50,6 +50,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
|
||||
#include "iCalAlarm+ActiveSync.h"
|
||||
#include "iCalRecurrenceRule+ActiveSync.h"
|
||||
#include "iCalTimeZone+ActiveSync.h"
|
||||
#include "NSDate+ActiveSync.h"
|
||||
@@ -83,6 +84,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
int v;
|
||||
|
||||
NSTimeZone *userTimeZone;
|
||||
userTimeZone = [[[context activeUser] userDefaults] timeZone];
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
[s appendFormat: @"<AllDayEvent xmlns=\"Calendar:\">%d</AllDayEvent>", ([self isAllDay] ? 1 : 0)];
|
||||
@@ -95,12 +99,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
|
||||
if ([self startDate])
|
||||
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>", [[self startDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
|
||||
{
|
||||
if ([self isAllDay])
|
||||
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
|
||||
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: ([userTimeZone secondsFromGMTForDate: [self startDate]])*-1]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
else
|
||||
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>", [[self startDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
|
||||
if ([self endDate])
|
||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
|
||||
{
|
||||
if ([self isAllDay])
|
||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
|
||||
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: ([userTimeZone secondsFromGMTForDate: [self endDate]])*-1]
|
||||
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
else
|
||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||
}
|
||||
|
||||
// Timezone
|
||||
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
|
||||
|
||||
@@ -138,7 +160,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
attendee = [attendees objectAtIndex: i];
|
||||
[s appendFormat: @"<Attendee_Email xmlns=\"Calendar:\">%@</Attendee_Email>", [attendee rfc822Email]];
|
||||
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [attendee cn]];
|
||||
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [[attendee cn] activeSyncRepresentationInContext: context]];
|
||||
|
||||
attendee_status = [self _attendeeStatus: attendee];
|
||||
|
||||
@@ -209,7 +231,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[s appendFormat: @"<Sensitivity xmlns=\"Calendar:\">%d</Sensitivity>", v];
|
||||
|
||||
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
|
||||
// TODO
|
||||
// TODO: improve this to handle more alarm types
|
||||
if ([self hasAlarms])
|
||||
{
|
||||
iCalAlarm *alarm;
|
||||
|
||||
alarm = [[self alarms] objectAtIndex: 0];
|
||||
[s appendString: [alarm activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
// Recurrence rules
|
||||
if ([self isRecurrent])
|
||||
@@ -356,6 +385,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (isAllDay)
|
||||
{
|
||||
tzOffset = [userTimeZone secondsFromGMTForDate: o];
|
||||
o = [o dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: tzOffset];
|
||||
[start setDate: o];
|
||||
[start setTimeZone: nil];
|
||||
}
|
||||
@@ -377,6 +410,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (isAllDay)
|
||||
{
|
||||
tzOffset = [userTimeZone secondsFromGMTForDate: o];
|
||||
o = [o dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: tzOffset];
|
||||
[end setDate: o];
|
||||
[end setTimeZone: nil];
|
||||
}
|
||||
@@ -390,6 +427,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// If an alarm is deinfed with an action != DISPLAY, we ignore the alarm - don't want to overwrite.
|
||||
//
|
||||
if ([self hasAlarms] && [[[[self alarms] objectAtIndex: 0] action] caseInsensitiveCompare: @"DISPLAY"] != NSOrderedSame)
|
||||
{
|
||||
// Ignore the alarm for now
|
||||
}
|
||||
else if ((o = [theValues objectForKey: @"Reminder"]))
|
||||
{
|
||||
// NOTE: Outlook sends a 15 min reminder (18 hour for allday) if no reminder is specified
|
||||
// although no default reminder is defined (File -> Options -> Clendar -> Calendar Options - > Default Reminders)
|
||||
//
|
||||
// http://answers.microsoft.com/en-us/office/forum/office_2013_release-outlook/desktop-outlook-calendar-creates-entries-with/9aef72d8-81bb-4a32-a6ab-bf7d216fb811?page=5&tm=1395690285088
|
||||
//
|
||||
iCalAlarm *alarm;
|
||||
|
||||
alarm = [[iCalAlarm alloc] init];
|
||||
[alarm takeActiveSyncValues: theValues inContext: context];
|
||||
|
||||
[self removeAllAlarms];
|
||||
[self addToAlarms: alarm];
|
||||
RELEASE(alarm);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We remove existing alarm since no reminder in the ActiveSync payload
|
||||
[self removeAllAlarms];
|
||||
}
|
||||
|
||||
// Recurrence
|
||||
if ((o = [theValues objectForKey: @"Recurrence"]))
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
if (occurrences[i])
|
||||
if (occurrences[0][i])
|
||||
v += (1 << i);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,111 @@
|
||||
2.2.4 (2014-05-29)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- new print option in Calendar module
|
||||
- now able to save unknown recipient emails to address book on send (#1496)
|
||||
|
||||
Enhancements
|
||||
- Sieve folder encoding is now configurable (#2622)
|
||||
- SOGo version is now displayed in preferences window (#2612)
|
||||
- report Sieve error when saving preferences (#1046)
|
||||
- added the SOGoMaximumSyncWindowSize system default to overwrite the
|
||||
maximum number of items returned during an ActiveSync sync operation
|
||||
- updated datepicker
|
||||
- addressbooks properties are now accessible from a popup window
|
||||
- extended events and tasks searches
|
||||
- updated Czech, French, Hungarian, Polish, Russian, Slovak, Spanish (Argentina), and Spanish (Spain) translations
|
||||
- added more sycned contact properties when using ActiveSync (#2775)
|
||||
- now possible to configure the default subscribed resource name using SOGoSubscriptionFolderFormat
|
||||
- now handle server-side folder updates using ActiveSync (#2688)
|
||||
- updated CKEditor to version 4.4.1
|
||||
|
||||
Bug fixes
|
||||
- fixed saved HTML content of draft when attaching a file
|
||||
- fixed text nodes of HTML content handler by encoding HTML entities
|
||||
- fixed iCal7 delegation issue with the "inbox" folder (#2489)
|
||||
- fixed birth date validity checks (#1636)
|
||||
- fixed URL handling (#2616)
|
||||
- improved folder rename operations using ActiveSync (#2700)
|
||||
- fixed SmartReply/Forward when ReplaceMime was omitted (#2680)
|
||||
- fixed wrong generation of weekly repetitive events with ActiveSync (#2654)
|
||||
- fixed incorrect XML data conversion with ActiveSync (#2695)
|
||||
- fixed display of events having a category with HTML entities (#2703)
|
||||
- fixed display of images in CSS background (#2437)
|
||||
- fixed limitation of Sieve script size (#2745)
|
||||
- fixed sync-token generation when no change was returned (#2492)
|
||||
- fixed the IMAP copy/move operation between subfolders in different accounts
|
||||
- fixed synchronization of seen/unseen status of msgs in Webmail (#2715)
|
||||
- fixed focus of popup windows open through a contextual menu with Firefox on Windows 7
|
||||
- fixed missing characters in shared folder names over ActiveSync (#2709)
|
||||
- fixed reply and forward mail templates for Brazilian Portuguese (#2738)
|
||||
- fixed newline in signature when forwarding a message as attachment in HTML mode (#2787)
|
||||
- fixed restoration of options (priority & return receipt) when editing a draft (#193)
|
||||
- fixed update of participation status via CalDAV (#2786)
|
||||
|
||||
2.2.3 (2014-04-03)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- updated Dutch, Hungarian, Russian and Spanish (Argentina) translations
|
||||
- initial support for ActiveSync event reminders support (#2681)
|
||||
- updated CKEditor to version 4.3.4
|
||||
|
||||
Bug fixes
|
||||
- fixed possible exception when retrieving the default event reminder value on 64bit architectures (#2678)
|
||||
- fixed calling unescapeHTML on null variables to avoid JavaScript exceptions in Contacts module
|
||||
- fixed detection of IMAP flags support on the client side (#2664)
|
||||
- fixed the ActiveSync issue marking all mails as read when downloading them
|
||||
- fixed ActiveSync's move operations not working for multiple selections (#2691)
|
||||
- fixed email validation regexp to allow gTLDs
|
||||
- improved all-day events support for ActiveSync (#2686)
|
||||
|
||||
2.2.2 (2014-03-21)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- updated French, Finnish, German and Spanish (Spain) translations
|
||||
- added sanitization support for Outlook/ActiveSync to circumvent Outlook bugs (#2667)
|
||||
- updated CKEditor to version 4.3.3
|
||||
- updated jQuery File Upload to version 9.5.7
|
||||
|
||||
Bug fixes
|
||||
- fixed possible exception when retrieving the default event reminder value on 64bit architectures (#2647, #2648)
|
||||
- disable file paste support in mail editor (#2641)
|
||||
- fixed copying/moving messages to a mail folder begining with a digit (#2658)
|
||||
- fixed unseen count for folders beginning with a digit and used in Sieve filters (#2652)
|
||||
- fixed decoding of HTML entities in reminder alerts (#2659)
|
||||
- fixed check for resource conflict when creating an event in the resource's calendar (#2541)
|
||||
- fixed construction of mail folders tree
|
||||
- fixed parsing of ORG attribute in cards (#2662)
|
||||
- disabled ActiveSync provisioning for now (#2663)
|
||||
- fixed messages move in Outlook which would create duplicates (#2650)
|
||||
- fixed translations for OtherUsersFolderName and SharedFoldersName folders (#2657)
|
||||
- fixed handling of accentuated characters when filtering contacts (#2656)
|
||||
- fixed classification icon of events (#2651)
|
||||
- fixed ActiveSync's SendMail with client version <= 12.1 (#2669)
|
||||
|
||||
2.2.1 (2014-03-07)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- updated Czech, Dutch, Finnish, and Hungarian translations
|
||||
- show current folder name in prompt dialog when renaming a mail folder
|
||||
|
||||
Bug fixes
|
||||
- fixed an issue with ActiveSync when the number of messages in the mailbox was greater than the window-size specified by the client
|
||||
- fixed sogo-tool operations on Sieve script (#2617)
|
||||
- fixed unsubscription when renaming an IMAP folder (#2630)
|
||||
- fixed sorting of events list by calendar name (#2629)
|
||||
- fixed wrong date format leading to Android email syncing issues (#2609)
|
||||
- fixed possible exception when retrieving the default event reminder value (#2624)
|
||||
- fixed encoding of mail folder name when creating a subfolder (#2637)
|
||||
- fixed returned date format for email messages in ActiveSync
|
||||
- fixed missing 'name part' in address for email messages in ActiveSync
|
||||
- fixed race condition when syncing huge amount of deleted messages over ActiveSync
|
||||
- fixed encoding of string as CSS identifier when the string starts with a digit
|
||||
- fixed auto-completion popupmenu when UID is a digit
|
||||
|
||||
2.2.0 (2014-02-24)
|
||||
------------------
|
||||
|
||||
|
||||
+1
-11
@@ -46,11 +46,8 @@ $(SOGOBACKEND)_OBJC_FILES += \
|
||||
MAPIStoreSamDBUtils.m \
|
||||
MAPIStoreUserContext.m \
|
||||
\
|
||||
SOGoMAPIObject.m \
|
||||
\
|
||||
SOGoMAPIDBObject.m \
|
||||
SOGoMAPIDBMessage.m \
|
||||
SOGoMAPIDBFolder.m \
|
||||
SOGoCacheGCSObject+MAPIStore.m \
|
||||
\
|
||||
MAPIStoreAppointmentWrapper.m \
|
||||
MAPIStoreAttachment.m \
|
||||
@@ -123,13 +120,6 @@ $(SOGOBACKEND)_OBJC_FILES += \
|
||||
iCalEvent+MAPIStore.m \
|
||||
iCalTimeZone+MAPIStore.m \
|
||||
\
|
||||
GCSSpecialQueries+OpenChange.m\
|
||||
\
|
||||
EOQualifier+MAPI.m \
|
||||
\
|
||||
EOBitmaskQualifier.m \
|
||||
\
|
||||
BSONCodec.m \
|
||||
RTFHandler.m
|
||||
|
||||
|
||||
|
||||
@@ -3,3 +3,6 @@ all:: MAPIStorePropertySelectors.m MAPIStorePropertySelectors.h
|
||||
MAPIStorePropertySelectors.m MAPIStorePropertySelectors.h: gen-property-selectors.py code-MAPIStorePropertySelectors.m code-MAPIStorePropertySelectors.h
|
||||
@echo " Auto-generating MAPIStorePropertySelectors.[hm]..."
|
||||
@$(PYTHON) ./gen-property-selectors.py -o MAPIStorePropertySelectors $(LIBMAPISTORE_CFLAGS)
|
||||
|
||||
distclean clean::
|
||||
-rm -f MAPIStorePropertySelectors.m MAPIStorePropertySelectors.h
|
||||
@@ -1,8 +1,6 @@
|
||||
/* MAPIStoreDBBaseContext.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2012-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
|
||||
@@ -32,7 +30,7 @@
|
||||
#import "MAPIStoreDBFolder.h"
|
||||
#import "MAPIStoreMapping.h"
|
||||
#import "MAPIStoreUserContext.h"
|
||||
#import "SOGoMAPIDBFolder.h"
|
||||
#import <SOGo/SOGoCacheGCSFolder.h>
|
||||
|
||||
#import "MAPIStoreDBBaseContext.h"
|
||||
|
||||
@@ -60,7 +58,7 @@ static Class MAPIStoreDBFolderK;
|
||||
|
||||
- (void) ensureContextFolder
|
||||
{
|
||||
SOGoMAPIDBFolder *currentFolder;
|
||||
SOGoCacheGCSFolder *currentFolder;
|
||||
NSArray *parts;
|
||||
NSMutableArray *folders;
|
||||
NSString *folderName;
|
||||
@@ -78,7 +76,7 @@ static Class MAPIStoreDBFolderK;
|
||||
folderName = [parts objectAtIndex: count];
|
||||
if ([folderName length] > 0)
|
||||
{
|
||||
currentFolder = [SOGoMAPIDBFolder objectWithName: folderName
|
||||
currentFolder = [SOGoCacheGCSFolder objectWithName: folderName
|
||||
inContainer: currentFolder];
|
||||
[folders addObject: currentFolder];
|
||||
}
|
||||
@@ -98,11 +96,11 @@ static Class MAPIStoreDBFolderK;
|
||||
|
||||
- (id) rootSOGoFolder
|
||||
{
|
||||
SOGoMAPIDBFolder *folder;
|
||||
SOGoCacheGCSFolder *folder;
|
||||
|
||||
[userContext ensureFolderTableExists];
|
||||
|
||||
folder = [SOGoMAPIDBFolder objectWithName: [isa MAPIModuleName]
|
||||
folder = [SOGoCacheGCSFolder objectWithName: [isa MAPIModuleName]
|
||||
inContainer: nil];
|
||||
[folder setTableUrl: [userContext folderTableURL]];
|
||||
// [folder reloadIfNeeded];
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#import <EOControl/EOQualifier.h>
|
||||
#import <SOGo/SOGoFolder.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import "EOQualifier+MAPI.h"
|
||||
#import <SOGo/EOQualifier+SOGoCacheObject.h>
|
||||
#import "MAPIStoreContext.h"
|
||||
#import "MAPIStoreDBFolderTable.h"
|
||||
#import "MAPIStoreDBMessage.h"
|
||||
@@ -40,7 +40,7 @@
|
||||
#import "MAPIStoreMapping.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "MAPIStoreUserContext.h"
|
||||
#import "SOGoMAPIDBFolder.h"
|
||||
#import <SOGo/SOGoCacheGCSFolder.h>
|
||||
#import "SOGoMAPIDBMessage.h"
|
||||
|
||||
#import "MAPIStoreDBFolder.h"
|
||||
@@ -49,7 +49,7 @@
|
||||
#include <mapistore/mapistore.h>
|
||||
#include <mapistore/mapistore_errors.h>
|
||||
|
||||
static Class EOKeyValueQualifierK, SOGoMAPIDBFolderK, MAPIStoreDBFolderK;
|
||||
static Class EOKeyValueQualifierK, SOGoCacheGCSFolderK, MAPIStoreDBFolderK;
|
||||
|
||||
static NSString *MAPIStoreRightReadItems = @"RightsReadItems";
|
||||
static NSString *MAPIStoreRightCreateItems = @"RightsCreateItems";
|
||||
@@ -66,7 +66,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
+ (void) initialize
|
||||
{
|
||||
EOKeyValueQualifierK = [EOKeyValueQualifier class];
|
||||
SOGoMAPIDBFolderK = [SOGoMAPIDBFolder class];
|
||||
SOGoCacheGCSFolderK = [SOGoCacheGCSFolder class];
|
||||
MAPIStoreDBFolderK = [MAPIStoreDBFolder class];
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
{
|
||||
enum mapistore_error rc;
|
||||
NSString *folderName, *nameInContainer;
|
||||
SOGoMAPIDBFolder *newFolder;
|
||||
SOGoCacheGCSFolder *newFolder;
|
||||
struct SPropValue *value;
|
||||
|
||||
value = get_SPropValue_SRow (aRow, PidTagDisplayName);
|
||||
@@ -111,7 +111,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
{
|
||||
nameInContainer = [NSString stringWithFormat: @"0x%.16"PRIx64,
|
||||
(unsigned long long) newFID];
|
||||
newFolder = [SOGoMAPIDBFolderK objectWithName: nameInContainer
|
||||
newFolder = [SOGoCacheGCSFolderK objectWithName: nameInContainer
|
||||
inContainer: sogoObject];
|
||||
[newFolder reloadIfNeeded];
|
||||
[[newFolder properties] setObject: folderName
|
||||
@@ -181,7 +181,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
[SOGoObject globallyUniqueObjectId]];
|
||||
fsObject = [SOGoMAPIDBMessage objectWithName: newKey
|
||||
inContainer: sogoObject];
|
||||
[fsObject setObjectType: MAPIDBObjectTypeMessage];
|
||||
[fsObject setObjectType: MAPIMessageCacheObject];
|
||||
[fsObject reloadIfNeeded];
|
||||
newMessage = [MAPIStoreDBMessage mapiStoreObjectWithSOGoObject: fsObject
|
||||
inContainer: self];
|
||||
@@ -198,7 +198,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
ownerUser = [[self userContext] sogoUser];
|
||||
if ([[context activeUser] isEqual: ownerUser]
|
||||
|| [self subscriberCanReadMessages])
|
||||
keys = [(SOGoMAPIDBFolder *) sogoObject childKeysOfType: MAPIDBObjectTypeMessage
|
||||
keys = [(SOGoCacheGCSFolder *) sogoObject childKeysOfType: MAPIMessageCacheObject
|
||||
includeDeleted: NO
|
||||
matchingQualifier: qualifier
|
||||
andSortOrderings: sortOrderings];
|
||||
@@ -211,7 +211,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
|
||||
- (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier
|
||||
andSortOrderings: (NSArray *) sortOrderings
|
||||
{
|
||||
return [dbFolder childKeysOfType: MAPIDBObjectTypeFolder
|
||||
return [dbFolder childKeysOfType: MAPIFolderCacheObject
|
||||
includeDeleted: NO
|
||||
matchingQualifier: qualifier
|
||||
andSortOrderings: sortOrderings];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* MAPIStoreFallbackContext.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2011-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
|
||||
@@ -26,7 +24,7 @@
|
||||
|
||||
#import "MAPIStoreUserContext.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
#import "SOGoMAPIDBFolder.h"
|
||||
#import <SOGo/SOGoCacheGCSFolder.h>
|
||||
|
||||
#import "MAPIStoreFallbackContext.h"
|
||||
|
||||
@@ -51,7 +49,7 @@
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
struct mapistore_contexts_list *firstContext = NULL, *context;
|
||||
SOGoMAPIDBFolder *root;
|
||||
SOGoCacheGCSFolder *root;
|
||||
NSArray *names;
|
||||
NSUInteger count, max;
|
||||
NSString *baseURL, *url, *name;
|
||||
@@ -70,7 +68,7 @@
|
||||
|
||||
/* Maybe emsmdbp_provisioning should be fixed in order to only take the uri
|
||||
returned above to avoid deleting its entries... */
|
||||
root = [SOGoMAPIDBFolder objectWithName: [self MAPIModuleName]
|
||||
root = [SOGoCacheGCSFolder objectWithName: [self MAPIModuleName]
|
||||
inContainer: nil];
|
||||
[root setOwner: userName];
|
||||
userContext = [MAPIStoreUserContext userContextWithUsername: userName
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* MAPIStoreFolder.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2012 Inverse inc
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2011-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
|
||||
@@ -38,7 +36,7 @@
|
||||
@class MAPIStoreMessageTable;
|
||||
@class MAPIStorePermissionsTable;
|
||||
@class SOGoFolder;
|
||||
@class SOGoMAPIDBFolder;
|
||||
@class SOGoCacheGCSFolder;
|
||||
@class SOGoMAPIDBMessage;
|
||||
|
||||
#import "MAPIStoreSOGoObject.h"
|
||||
@@ -50,7 +48,7 @@
|
||||
// NSArray *faiMessageKeys;
|
||||
// NSArray *folderKeys;
|
||||
|
||||
SOGoMAPIDBFolder *dbFolder;
|
||||
SOGoCacheGCSFolder *dbFolder;
|
||||
// SOGoMAPIDBFolder *faiFolder;
|
||||
// SOGoMAPIDBFolder *propsFolder;
|
||||
// SOGoMAPIDBMessage *propsMessage;
|
||||
@@ -60,7 +58,7 @@
|
||||
|
||||
- (void) setupAuxiliaryObjects;
|
||||
|
||||
- (SOGoMAPIDBFolder *) dbFolder;
|
||||
- (SOGoCacheGCSFolder *) dbFolder;
|
||||
|
||||
- (NSArray *) activeMessageTables;
|
||||
- (NSArray *) activeFAIMessageTables;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* MAPIStoreFolder.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2012 Inverse inc
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2011-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
|
||||
@@ -51,8 +49,9 @@
|
||||
#import "NSDate+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "SOGoMAPIDBFolder.h"
|
||||
#import <SOGo/SOGoCacheGCSFolder.h>
|
||||
#import "SOGoMAPIDBMessage.h"
|
||||
#import "SOGoCacheGCSObject+MAPIStore.h"
|
||||
|
||||
#include <gen_ndr/exchange.h>
|
||||
|
||||
@@ -120,7 +119,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
[userContext ensureFolderTableExists];
|
||||
|
||||
ASSIGN (dbFolder,
|
||||
[SOGoMAPIDBFolder objectWithName: folderName
|
||||
[SOGoCacheGCSFolder objectWithName: folderName
|
||||
inContainer: [container dbFolder]]);
|
||||
[dbFolder setTableUrl: [userContext folderTableURL]];
|
||||
if (!container && [path length] > 0)
|
||||
@@ -138,7 +137,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
// ASSIGN (propsMessage,
|
||||
// [SOGoMAPIDBMessage objectWithName: @"properties.plist"
|
||||
// inContainer: dbFolder]);
|
||||
// [propsMessage setObjectType: MAPIDBObjectTypeInternal];
|
||||
// [propsMessage setObjectType: MAPIInternalCacheObject];
|
||||
// [propsMessage reloadIfNeeded];
|
||||
[properties release];
|
||||
properties = [dbFolder properties];
|
||||
@@ -192,7 +191,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (SOGoMAPIDBFolder *) dbFolder
|
||||
- (SOGoCacheGCSFolder *) dbFolder
|
||||
{
|
||||
return dbFolder;
|
||||
}
|
||||
@@ -1221,7 +1220,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
- (NSArray *) faiMessageKeysMatchingQualifier: (EOQualifier *) qualifier
|
||||
andSortOrderings: (NSArray *) sortOrderings
|
||||
{
|
||||
return [dbFolder childKeysOfType: MAPIDBObjectTypeFAI
|
||||
return [dbFolder childKeysOfType: MAPIFAICacheObject
|
||||
includeDeleted: NO
|
||||
matchingQualifier: qualifier
|
||||
andSortOrderings: sortOrderings];
|
||||
@@ -1531,7 +1530,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
newKey = [NSString stringWithFormat: @"%@.plist",
|
||||
[SOGoObject globallyUniqueObjectId]];
|
||||
dbObject = [SOGoMAPIDBMessage objectWithName: newKey inContainer: dbFolder];
|
||||
[dbObject setObjectType: MAPIDBObjectTypeFAI];
|
||||
[dbObject setObjectType: MAPIFAICacheObject];
|
||||
[dbObject setIsNew: YES];
|
||||
newMessage = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: dbObject
|
||||
inContainer: self];
|
||||
|
||||
@@ -74,7 +74,7 @@ static Class NSNumberK;
|
||||
ASSIGN (versionsMessage,
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIDBObjectTypeInternal];
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
#import "SOGoMAPIDBMessage.h"
|
||||
#import "SOGoMAPIDBFolder.h"
|
||||
#import <SOGo/SOGoCacheGCSFolder.h>
|
||||
|
||||
#import "MAPIStoreMailVolatileMessage.h"
|
||||
|
||||
@@ -106,7 +106,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
ASSIGN (versionsMessage,
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIDBObjectTypeInternal];
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
}
|
||||
|
||||
- (BOOL) ensureFolderExists
|
||||
@@ -1172,9 +1172,9 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
|
||||
- (MAPIStoreMessage *) createMessage
|
||||
{
|
||||
SOGoMAPIObject *childObject;
|
||||
SOGoCacheObject *childObject;
|
||||
|
||||
childObject = [SOGoMAPIObject objectWithName: [SOGoMAPIObject
|
||||
childObject = [SOGoCacheObject objectWithName: [SOGoCacheObject
|
||||
globallyUniqueObjectId]
|
||||
inContainer: sogoObject];
|
||||
return [MAPIStoreMailVolatileMessage
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
#import "SOGoMAPIObject.h"
|
||||
#import <SOGo/SOGoCacheObject.h>
|
||||
|
||||
#import "MAPIStoreMailVolatileMessage.h"
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <EOControl/EOQualifier.h>
|
||||
|
||||
#import "EOBitmaskQualifier.h"
|
||||
#import <SOGo/EOBitmaskQualifier.h>
|
||||
#import "MAPIStoreActiveTables.h"
|
||||
#import "MAPIStoreObject.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#import <Mailer/SOGoMailAccount.h>
|
||||
#import <Mailer/SOGoMailAccounts.h>
|
||||
|
||||
#import "GCSSpecialQueries+OpenChange.h"
|
||||
#import <SOGo/GCSSpecialQueries+SOGoCacheObject.h>
|
||||
#import "MAPIApplication.h"
|
||||
#import "MAPIStoreAuthenticator.h"
|
||||
#import "MAPIStoreMapping.h"
|
||||
@@ -295,7 +295,7 @@ static NSMapTable *contextsTable = nil;
|
||||
{
|
||||
/* If "OCSFolderInfoURL" is properly configured, we must have 5
|
||||
parts in this url. */
|
||||
ocFSTableName = [NSString stringWithFormat: @"socfs_%@",
|
||||
ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@",
|
||||
[username asCSSIdentifier]];
|
||||
[parts replaceObjectAtIndex: 4 withObject: ocFSTableName];
|
||||
folderTableURL
|
||||
@@ -329,7 +329,7 @@ static NSMapTable *contextsTable = nil;
|
||||
tableName]])
|
||||
{
|
||||
queries = [channel specialQueries];
|
||||
query = [queries createOpenChangeFSTableWithName: tableName];
|
||||
query = [queries createSOGoCacheGCSFolderTableWithName: tableName];
|
||||
if ([channel evaluateExpressionX: query])
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"could not create special table '%@'", tableName];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* dbmsgdump.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2012 Inverse inc
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2011-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
|
||||
@@ -33,7 +31,7 @@
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
|
||||
#import "BSONCodec.h"
|
||||
#import <SOGo/BSONCodec.h>
|
||||
|
||||
const char *indentationStep = " ";
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SOGoCacheGCSObject+MAPIStore.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 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
|
||||
* the Free Software Foundation; either version 3, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef SOGOCACHEGCSOBJECTMAPISTORE
|
||||
#define SOGOCACHEGCSOBJECTMAPISTORE
|
||||
|
||||
#include <SOGo/SOGoCacheGCSObject.h>
|
||||
|
||||
@interface SOGoCacheGCSObject (MAPIStore)
|
||||
|
||||
- (Class) mapistoreMessageClass;
|
||||
|
||||
@end
|
||||
|
||||
#endif // SOGOCACHEGCSOBJECTMAPISTORE
|
||||
@@ -0,0 +1,68 @@
|
||||
/* SOGoCacheGCSObject+MAPIStore.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 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
|
||||
* the Free Software Foundation; either version 3, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#include "MAPIStoreTypes.h"
|
||||
|
||||
#include "SOGoCacheGCSObject+MAPIStore.h"
|
||||
|
||||
@implementation SOGoCacheGCSObject (MAPIStore)
|
||||
|
||||
- (Class) mapistoreMessageClass
|
||||
{
|
||||
NSString *className, *mapiMsgClass;
|
||||
|
||||
switch (objectType)
|
||||
{
|
||||
case MAPIMessageCacheObject:
|
||||
mapiMsgClass = [properties
|
||||
objectForKey: MAPIPropertyKey (PidTagMessageClass)];
|
||||
if (mapiMsgClass)
|
||||
{
|
||||
if ([mapiMsgClass isEqualToString: @"IPM.StickyNote"])
|
||||
className = @"MAPIStoreNotesMessage";
|
||||
else
|
||||
className = @"MAPIStoreDBMessage";
|
||||
//[self logWithFormat: @"PidTagMessageClass = '%@', returning '%@'",
|
||||
// mapiMsgClass, className];
|
||||
}
|
||||
else
|
||||
{
|
||||
//[self warnWithFormat: @"PidTagMessageClass is not set, falling back"
|
||||
// @" to 'MAPIStoreDBMessage'"];
|
||||
className = @"MAPIStoreDBMessage";
|
||||
}
|
||||
break;
|
||||
case MAPIFAICacheObject:
|
||||
className = @"MAPIStoreFAIMessage";
|
||||
break;
|
||||
default:
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"message class should not be queried for objects"
|
||||
@" of type '%d'", objectType];
|
||||
}
|
||||
|
||||
return NSClassFromString (className);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,8 +1,6 @@
|
||||
/* SOGoMAPIDBMessage.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2012-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
|
||||
@@ -23,12 +21,12 @@
|
||||
#ifndef SOGOMAPIDBMESSAGE_H
|
||||
#define SOGOMAPIDBMESSAGE_H
|
||||
|
||||
#import "SOGoMAPIDBObject.h"
|
||||
#import <SOGo/SOGoCacheGCSObject.h>
|
||||
|
||||
@class NSDate;
|
||||
@class NSString;
|
||||
|
||||
@interface SOGoMAPIDBMessage : SOGoMAPIDBObject
|
||||
@interface SOGoMAPIDBMessage : SOGoCacheGCSObject
|
||||
@end
|
||||
|
||||
#endif /* SOGOMAPIDBMESSAGE_H */
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "SOGoMAPIDBFolder.h"
|
||||
#import <SOGo/SOGoCacheGCSFolder.h>
|
||||
|
||||
#import "SOGoMAPIDBMessage.h"
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
|
||||
#import "MAPIStoreUserContext.h"
|
||||
#import "SOGoMAPIDBObject.h"
|
||||
#import <SOGo/SOGoCacheGCSObject.h>
|
||||
|
||||
#import "NSObject+PropertyList.m"
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ static NSArray *privilegedTagNames = nil;
|
||||
|
||||
if (!privilegedTagNames)
|
||||
{
|
||||
privilegedTagNames = [NSArray arrayWithObjects: @"ADR", @"N", @"RRULE", nil];
|
||||
privilegedTagNames = [NSArray arrayWithObjects: @"ADR", @"N", @"RRULE", @"ORG", nil];
|
||||
RETAIN(privilegedTagNames);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaul
|
||||
# - 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 socfs_ table for the username.
|
||||
# - Delete the sogo_cache_folder_ table for the username.
|
||||
|
||||
def usage():
|
||||
print """
|
||||
@@ -210,7 +210,7 @@ On RHEL, install it using 'yum install MySQL-python'"""
|
||||
|
||||
conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
|
||||
c=conn.cursor()
|
||||
tablename="socfs_%s" % (username)
|
||||
tablename="sogo_cache_folder_%s" % (username)
|
||||
c.execute("TRUNCATE TABLE %s" % tablename)
|
||||
print "Table %s emptied"
|
||||
|
||||
@@ -225,7 +225,7 @@ 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 = "socfs_%s" % username
|
||||
tablename = "sogo_cache_folder_%s" % username
|
||||
conn.query("DELETE FROM %s" % tablename)
|
||||
print "Table '%s' emptied" % tablename
|
||||
|
||||
@@ -278,7 +278,7 @@ 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 socfs_%s table should be truncated manually" % (username))
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2013 Inverse inc.
|
||||
Copyright (C) 2007-2014 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
@@ -1280,13 +1280,20 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
[baseWhere addObject: privacySQLString];
|
||||
|
||||
if ([title length])
|
||||
[baseWhere
|
||||
addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
|
||||
if ([filters length])
|
||||
{
|
||||
if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"])
|
||||
{
|
||||
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')",
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
|
||||
}
|
||||
}
|
||||
else
|
||||
[baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
|
||||
|
||||
if ([filters length])
|
||||
[baseWhere addObject: [NSString stringWithFormat: @"(%@)", filters]];
|
||||
|
||||
/* prepare mandatory fields */
|
||||
|
||||
fields = [NSMutableArray arrayWithArray: _fields];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
/* SOGoAppointmentFolders.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2013 Inverse inc.
|
||||
* Copyright (C) 2007-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
|
||||
@@ -56,13 +56,6 @@
|
||||
|
||||
#import "SOGoAppointmentFolders.h"
|
||||
|
||||
@interface SOGoParentFolder (Private)
|
||||
|
||||
- (NSException *) _fetchPersonalFolders: (NSString *) sql
|
||||
withChannel: (EOAdaptorChannel *) fc;
|
||||
|
||||
@end
|
||||
|
||||
static SoSecurityManager *sm = nil;
|
||||
|
||||
@implementation SOGoAppointmentFolders
|
||||
@@ -596,8 +589,9 @@ static SoSecurityManager *sm = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSException *) _fetchPersonalFolders: (NSString *) sql
|
||||
withChannel: (EOAdaptorChannel *) fc
|
||||
- (NSException *) fetchSpecialFolders: (NSString *) sql
|
||||
withChannel: (EOAdaptorChannel *) fc
|
||||
andFolderType: (SOGoFolderType) folderType
|
||||
{
|
||||
BOOL isWebRequest;
|
||||
NSException *error;
|
||||
@@ -607,7 +601,7 @@ static SoSecurityManager *sm = nil;
|
||||
SOGoWebAppointmentFolder *webFolder;
|
||||
NSString *name;
|
||||
|
||||
error = [super _fetchPersonalFolders: sql withChannel: fc];
|
||||
error = [super fetchSpecialFolders: sql withChannel: fc andFolderType: folderType];
|
||||
if (!error)
|
||||
{
|
||||
isWebRequest = [[context request] handledByDefaultHandler];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* SOGoAppointmentInboxFolder.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2010-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
|
||||
|
||||
@@ -148,14 +148,14 @@
|
||||
object = [folder lookupName: nameInContainer
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
if ([object isKindOfClass: [NSException class]])
|
||||
if ([object isKindOfClass: [NSException class]] || [object isNew])
|
||||
{
|
||||
possibleName = [folder resourceNameForEventUID: eventUID];
|
||||
if (possibleName)
|
||||
{
|
||||
object = [folder lookupName: possibleName
|
||||
inContext: context acquire: NO];
|
||||
if ([object isKindOfClass: [NSException class]])
|
||||
if ([object isKindOfClass: [NSException class]] || [object isNew])
|
||||
object = nil;
|
||||
}
|
||||
else
|
||||
@@ -453,11 +453,7 @@
|
||||
currentUser = [context activeUser];
|
||||
if (!activeUserIsOwner && ![currentUser isSuperUser])
|
||||
{
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
if ([ownerUser isResource])
|
||||
{
|
||||
[attendees addObject: owner];
|
||||
}
|
||||
[attendees addObject: owner];
|
||||
}
|
||||
|
||||
enumerator = [attendees objectEnumerator];
|
||||
@@ -556,52 +552,52 @@
|
||||
currentAttendee = nil;
|
||||
}
|
||||
|
||||
if (currentAttendee)
|
||||
if ([fbInfo count])
|
||||
{
|
||||
if ([fbInfo count])
|
||||
// If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit
|
||||
// is imposed) or if numberOfSimultaneousBookings is greater than the number of
|
||||
// overlapping events
|
||||
if ([user numberOfSimultaneousBookings] == 0 ||
|
||||
[user numberOfSimultaneousBookings] > [fbInfo count])
|
||||
{
|
||||
// If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit
|
||||
// is imposed) or if numberOfSimultaneousBookings is greater than the number of
|
||||
// overlapping events
|
||||
if ([user numberOfSimultaneousBookings] == 0 ||
|
||||
[user numberOfSimultaneousBookings] > [fbInfo count])
|
||||
if (currentAttendee)
|
||||
{
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
}
|
||||
else
|
||||
{
|
||||
iCalCalendar *calendar;
|
||||
NSDictionary *values;
|
||||
NSString *reason;
|
||||
iCalEvent *event;
|
||||
|
||||
calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]];
|
||||
event = [[calendar events] lastObject];
|
||||
|
||||
values = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings",
|
||||
[user cn], @"Cn",
|
||||
[user systemEmail], @"SystemEmail",
|
||||
([event summary] ? [event summary] : @""), @"EventTitle",
|
||||
[[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate",
|
||||
nil];
|
||||
|
||||
reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]];
|
||||
|
||||
return [NSException exceptionWithHTTPStatus:403
|
||||
reason: reason];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No conflict, we auto-accept. We do this for resources automatically if no
|
||||
// double-booking is observed. If it's not the desired behavior, just don't
|
||||
// set the resource as one!
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
iCalCalendar *calendar;
|
||||
NSDictionary *values;
|
||||
NSString *reason;
|
||||
iCalEvent *event;
|
||||
|
||||
calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]];
|
||||
event = [[calendar events] lastObject];
|
||||
|
||||
values = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings",
|
||||
[user cn], @"Cn",
|
||||
[user systemEmail], @"SystemEmail",
|
||||
([event summary] ? [event summary] : @""), @"EventTitle",
|
||||
[[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate",
|
||||
nil];
|
||||
|
||||
reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]];
|
||||
|
||||
return [NSException exceptionWithHTTPStatus: 403
|
||||
reason: reason];
|
||||
}
|
||||
}
|
||||
else if (currentAttendee)
|
||||
{
|
||||
// No conflict, we auto-accept. We do this for resources automatically if no
|
||||
// double-booking is observed. If it's not the desired behavior, just don't
|
||||
// set the resource as one!
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
SOGoAppointmentInboxFolder = {
|
||||
superclass = "SOGoAppointmentFolder";
|
||||
defaultRoles = {
|
||||
"Access Contents Information" = ( "Owner" );
|
||||
"Access Contents Information" = ( "Owner", "AuthorizedSubscriber" );
|
||||
};
|
||||
};
|
||||
SOGoCalendarComponent = {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Osobní kontakty";
|
||||
"Collected Address Book" = "Sebrané kontakty";
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Personal Address Book";
|
||||
"Collected Address Book" = "Collected Address Book";
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Carnet d'adresses personnel";
|
||||
"Collected Address Book" = "Adresses collectées";
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Személyes címjegyzék";
|
||||
"Collected Address Book" = "Összegyűjtöt címek jegyzéke";
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Osobista książka adresowa";
|
||||
"Collected Address Book" = "Zbierana Książka Adresowa";
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Личная адресная книга";
|
||||
"Collected Address Book" = "Собранные адреса";
|
||||
@@ -25,12 +25,19 @@
|
||||
|
||||
@interface SOGoContactFolders : SOGoParentFolder
|
||||
|
||||
- (NSString *) defaultFolderName;
|
||||
- (NSString *) collectedFolderName;
|
||||
|
||||
- (NSException *) renameLDAPAddressBook: (NSString *) sourceID
|
||||
withDisplayName: (NSString *) newDisplayName;
|
||||
- (NSException *) removeLDAPAddressBook: (NSString *) sourceID;
|
||||
|
||||
- (NSDictionary *) systemSources;
|
||||
|
||||
- (NSArray *) allContactsFromFilter: (NSString *) theFilter
|
||||
excludeGroups: (BOOL) excludeGroups
|
||||
excludeLists: (BOOL) excludeLists;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* SOGOCONTACTFOLDERS_H */
|
||||
@@ -22,12 +22,18 @@
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSSortDescriptor.h>
|
||||
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <DOM/DOMElement.h>
|
||||
#import <DOM/DOMProtocols.h>
|
||||
|
||||
#import <GDLContentStore/GCSChannelManager.h>
|
||||
#import <GDLContentStore/GCSFolderManager.h>
|
||||
#import <GDLContentStore/NSURL+GCS.h>
|
||||
#import <GDLAccess/EOAdaptorChannel.h>
|
||||
|
||||
#import <SOGo/NSObject+DAV.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
@@ -40,10 +46,17 @@
|
||||
|
||||
#import "SOGoContactFolders.h"
|
||||
|
||||
Class SOGoContactSourceFolderK;
|
||||
|
||||
#define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav"
|
||||
|
||||
@implementation SOGoContactFolders
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
SOGoContactSourceFolderK = [SOGoContactSourceFolder class];
|
||||
}
|
||||
|
||||
+ (NSString *) gcsFolderType
|
||||
{
|
||||
return @"Contact";
|
||||
@@ -110,6 +123,38 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSException *) appendCollectedSources
|
||||
{
|
||||
GCSChannelManager *cm;
|
||||
EOAdaptorChannel *fc;
|
||||
NSURL *folderLocation;
|
||||
NSString *sql, *gcsFolderType;
|
||||
NSException *error;
|
||||
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
folderLocation = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
|
||||
fc = [cm acquireOpenChannelForURL: folderLocation];
|
||||
if ([fc isOpen])
|
||||
{
|
||||
gcsFolderType = [[self class] gcsFolderType];
|
||||
|
||||
sql = [NSString stringWithFormat: (@"SELECT c_path4 FROM %@"
|
||||
@" WHERE c_path2 = '%@'"
|
||||
@" AND c_folder_type = '%@'"),
|
||||
[folderLocation gcsTableName], owner, gcsFolderType];
|
||||
|
||||
error = [super fetchSpecialFolders: sql withChannel: fc andFolderType: SOGoCollectedFolder];
|
||||
|
||||
[cm releaseChannel: fc];
|
||||
}
|
||||
else
|
||||
error = [NSException exceptionWithName: @"SOGoDBException"
|
||||
reason: @"database connection could not be open"
|
||||
userInfo: nil];
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
- (NSDictionary *) systemSources
|
||||
{
|
||||
NSMutableDictionary *systemSources;
|
||||
@@ -225,7 +270,7 @@
|
||||
|
||||
if ([sourceID isEqualToString: @"personal"])
|
||||
result = [NSException exceptionWithHTTPStatus: 403
|
||||
reason: @"folder 'personal' cannot be deleted"];
|
||||
reason: (@"folder '%@' cannot be deleted", sourceID)];
|
||||
else
|
||||
{
|
||||
result = nil;
|
||||
@@ -250,6 +295,11 @@
|
||||
return [self labelForKey: @"Personal Address Book"];
|
||||
}
|
||||
|
||||
- (NSString *) collectedFolderName
|
||||
{
|
||||
return [self labelForKey: @"Collected Address Book"];
|
||||
}
|
||||
|
||||
- (NSArray *) toManyRelationshipKeys
|
||||
{
|
||||
NSMutableArray *keys;
|
||||
@@ -371,4 +421,89 @@
|
||||
asWebDAVValue];
|
||||
}
|
||||
|
||||
- (NSArray *) allContactsFromFilter: (NSString *) theFilter
|
||||
excludeGroups: (BOOL) excludeGroups
|
||||
excludeLists: (BOOL) excludeLists
|
||||
{
|
||||
SOGoFolder <SOGoContactFolder> *folder;
|
||||
NSString *mail, *domain;
|
||||
NSArray *folders, *contacts, *descriptors, *sortedContacts;
|
||||
NSMutableArray *sortedFolders;
|
||||
NSMutableDictionary *contact, *uniqueContacts;
|
||||
unsigned int i, j, max;
|
||||
NSSortDescriptor *commonNameDescriptor;
|
||||
|
||||
// NSLog(@"Search all contacts: %@", searchText);
|
||||
|
||||
domain = [[context activeUser] domain];
|
||||
folders = nil;
|
||||
NS_DURING
|
||||
folders = [self subFolders];
|
||||
NS_HANDLER
|
||||
/* We need to specifically test for @"SOGoDBException", which is
|
||||
raised explicitly in SOGoParentFolder. Any other exception should
|
||||
be re-raised. */
|
||||
if ([[localException name] isEqualToString: @"SOGoDBException"])
|
||||
folders = nil;
|
||||
else
|
||||
[localException raise];
|
||||
NS_ENDHANDLER;
|
||||
max = [folders count];
|
||||
sortedFolders = [NSMutableArray arrayWithCapacity: max];
|
||||
uniqueContacts = [NSMutableDictionary dictionary];
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
folder = [folders objectAtIndex: i];
|
||||
/* We first search in LDAP folders (in case of duplicated entries in GCS folders) */
|
||||
if ([folder isKindOfClass: SOGoContactSourceFolderK])
|
||||
[sortedFolders insertObject: folder atIndex: 0];
|
||||
else
|
||||
[sortedFolders addObject: folder];
|
||||
}
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
folder = [sortedFolders objectAtIndex: i];
|
||||
//NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]);
|
||||
contacts = [folder lookupContactsWithFilter: theFilter
|
||||
onCriteria: @"name_or_address"
|
||||
sortBy: @"c_cn"
|
||||
ordering: NSOrderedAscending
|
||||
inDomain: domain];
|
||||
for (j = 0; j < [contacts count]; j++)
|
||||
{
|
||||
contact = [contacts objectAtIndex: j];
|
||||
mail = [contact objectForKey: @"c_mail"];
|
||||
//NSLog(@" found %@ (%@) ? %@", [contact objectForKey: @"c_name"], mail,
|
||||
// [contact description]);
|
||||
if (!excludeLists && [[contact objectForKey: @"c_component"]
|
||||
isEqualToString: @"vlist"])
|
||||
{
|
||||
[contact setObject: [folder nameInContainer]
|
||||
forKey: @"container"];
|
||||
[uniqueContacts setObject: contact
|
||||
forKey: [contact objectForKey: @"c_name"]];
|
||||
}
|
||||
else if ([mail length]
|
||||
&& [uniqueContacts objectForKey: mail] == nil
|
||||
&& !(excludeGroups && [contact objectForKey: @"isGroup"]))
|
||||
[uniqueContacts setObject: contact forKey: mail];
|
||||
}
|
||||
}
|
||||
if ([uniqueContacts count] > 0)
|
||||
{
|
||||
// Sort the contacts by display name
|
||||
commonNameDescriptor = [[NSSortDescriptor alloc] initWithKey: @"c_cn"
|
||||
ascending:YES];
|
||||
descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil];
|
||||
[commonNameDescriptor release];
|
||||
sortedContacts = [[uniqueContacts allValues]
|
||||
sortedArrayUsingDescriptors: descriptors];
|
||||
}
|
||||
else
|
||||
sortedContacts = [NSArray array];
|
||||
|
||||
return sortedContacts;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Osobné kontakty";
|
||||
"Collected Address Book" = "Pozbierané osobné kontakty";
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Libreta personal de direcciones";
|
||||
"Collected Address Book" = "Direcciones recopiladas";
|
||||
@@ -1 +1,2 @@
|
||||
"Personal Address Book" = "Libreta personal de direcciones";
|
||||
"Collected Address Book" = "Libreta de direcciones recogidas";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "فلاتر";
|
||||
"OtherUsersFolderName" = "مستخدمين أخرين";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Filtros";
|
||||
"OtherUsersFolderName" = "Outros Usuários";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Filtres";
|
||||
"OtherUsersFolderName" = "Altres usuaris";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Ostatní uživatelé";
|
||||
"SharedFoldersName" = "Sdílené složky";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Filtre";
|
||||
"OtherUsersFolderName" = "Andre brugere";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Andere Gebruikers";
|
||||
"SharedFoldersName" = "Gedeelde Mappen";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Other Users";
|
||||
"SharedFoldersName" = "Shared Folders";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Muut käyttäjät";
|
||||
"SharedFoldersName" = "Jaetut kansiot";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Autres utilisateurs";
|
||||
"SharedFoldersName" = "Dossiers partagés";
|
||||
@@ -92,7 +92,7 @@ Mailer_RESOURCE_FILES += \
|
||||
SOGoMailWelshReply.wo
|
||||
|
||||
|
||||
Mailer_LANGUAGES = English French
|
||||
Mailer_LANGUAGES = Arabic BrazilianPortuguese Catalan Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian NorwegianBokmal NorwegianNynorsk Polish Russian Slovak SpanishSpain SpanishArgentina Swedish Ukrainian Welsh
|
||||
|
||||
Mailer_LOCALIZED_RESOURCE_FILES = Localizable.strings
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Andere Benutzer";
|
||||
"SharedFoldersName" = "Gemeinsame Ordner";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Egyéb felhasználók";
|
||||
"SharedFoldersName" = "Megosztott mappák";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Síur";
|
||||
"OtherUsersFolderName" = "Aðrir Notendur";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Filtri";
|
||||
"OtherUsersFolderName" = "Altri Utenti";
|
||||
@@ -417,7 +417,15 @@
|
||||
{
|
||||
if (!ignoreContent)
|
||||
{
|
||||
[result appendString: [NSString stringWithCharacters: characters length: length]];
|
||||
// Append a text node
|
||||
if (ignoreContentTags)
|
||||
// We are converting a HTML message to plain text (htmlToTextContentHandler):
|
||||
// include the HTML tags in the text
|
||||
[result appendString: [NSString stringWithCharacters: characters length: length]];
|
||||
else
|
||||
// We are sanitizing an HTML message (sanitizerContentHandler):
|
||||
// escape the HTML entitites so they are visible
|
||||
[result appendString: [[NSString stringWithCharacters: characters length: length] stringByEscapingHTMLString]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Filter";
|
||||
"OtherUsersFolderName" = "Andre brukere";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Filter";
|
||||
"OtherUsersFolderName" = "Other Users";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Inni użytkownicy";
|
||||
"SharedFoldersName" = "Foldery współdzielone";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Другие пользователи";
|
||||
"SharedFoldersName" = "Общие папки";
|
||||
@@ -102,6 +102,8 @@
|
||||
- (NSData *) mimeMessageAsData;
|
||||
|
||||
/* operations */
|
||||
- (NSArray *) allRecipients;
|
||||
- (NSArray *) allBareRecipients;
|
||||
|
||||
- (NSException *) delete;
|
||||
- (NSException *) sendMail;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#import <NGImap4/NGImap4Client.h>
|
||||
#import <NGImap4/NGImap4Envelope.h>
|
||||
#import <NGImap4/NGImap4EnvelopeAddress.h>
|
||||
#import <NGMail/NGMailAddressParser.h>
|
||||
#import <NGMail/NGMimeMessage.h>
|
||||
#import <NGMail/NGMimeMessageGenerator.h>
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
@@ -63,6 +64,11 @@
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
|
||||
#import <NGCards/NGVCard.h>
|
||||
|
||||
#import <Contacts/SOGoContactFolders.h>
|
||||
#import <Contacts/SOGoContactGCSEntry.h>
|
||||
|
||||
#import "NSData+Mail.h"
|
||||
#import "NSString+Mail.h"
|
||||
#import "SOGoMailAccount.h"
|
||||
@@ -260,7 +266,7 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
id headerValue;
|
||||
unsigned int count;
|
||||
NSString *messageID, *priority, *pureSender, *replyTo;
|
||||
NSString *messageID, *priority, *pureSender, *replyTo, *receipt;
|
||||
|
||||
for (count = 0; count < 8; count++)
|
||||
{
|
||||
@@ -279,26 +285,57 @@ static NSString *userAgent = nil;
|
||||
[headers setObject: messageID forKey: @"message-id"];
|
||||
}
|
||||
|
||||
priority = [newHeaders objectForKey: @"priority"];
|
||||
if (!priority || [priority isEqualToString: @"NORMAL"])
|
||||
priority = [newHeaders objectForKey: @"X-Priority"];
|
||||
if (priority)
|
||||
{
|
||||
[headers removeObjectForKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"HIGHEST"])
|
||||
{
|
||||
[headers setObject: @"1 (Highest)" forKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"HIGH"])
|
||||
{
|
||||
[headers setObject: @"2 (High)" forKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"LOW"])
|
||||
{
|
||||
[headers setObject: @"4 (Low)" forKey: @"X-Priority"];
|
||||
// newHeaders come from MIME message; convert X-Priority to Web representation
|
||||
[headers setObject: priority forKey: @"X-Priority"];
|
||||
[headers removeObjectForKey: @"priority"];
|
||||
if ([priority isEqualToString: @"1 (Highest)"])
|
||||
{
|
||||
[headers setObject: @"HIGHEST" forKey: @"priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"2 (High)"])
|
||||
{
|
||||
[headers setObject: @"HIGH" forKey: @"priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"4 (Low)"])
|
||||
{
|
||||
[headers setObject: @"LOW" forKey: @"priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"5 (Lowest)"])
|
||||
{
|
||||
[headers setObject: @"LOWEST" forKey: @"priority"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[headers setObject: @"5 (Lowest)" forKey: @"X-Priority"];
|
||||
// newHeaders come from Web form; convert priority to MIME header representation
|
||||
priority = [newHeaders objectForKey: @"priority"];
|
||||
if (!priority || [priority isEqualToString: @"NORMAL"])
|
||||
{
|
||||
[headers removeObjectForKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"HIGHEST"])
|
||||
{
|
||||
[headers setObject: @"1 (Highest)" forKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"HIGH"])
|
||||
{
|
||||
[headers setObject: @"2 (High)" forKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority isEqualToString: @"LOW"])
|
||||
{
|
||||
[headers setObject: @"4 (Low)" forKey: @"X-Priority"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[headers setObject: @"5 (Lowest)" forKey: @"X-Priority"];
|
||||
}
|
||||
if (priority)
|
||||
{
|
||||
[headers setObject: priority forKey: @"priority"];
|
||||
}
|
||||
}
|
||||
|
||||
replyTo = [headers objectForKey: @"replyTo"];
|
||||
@@ -308,14 +345,30 @@ static NSString *userAgent = nil;
|
||||
}
|
||||
[headers removeObjectForKey: @"replyTo"];
|
||||
|
||||
if ([[newHeaders objectForKey: @"receipt"] isEqualToString: @"true"])
|
||||
receipt = [newHeaders objectForKey: @"Disposition-Notification-To"];
|
||||
if ([receipt length] > 0)
|
||||
{
|
||||
pureSender = [[newHeaders objectForKey: @"from"] pureEMailAddress];
|
||||
if (pureSender)
|
||||
[headers setObject: pureSender forKey: @"Disposition-Notification-To"];
|
||||
[headers setObject: @"true" forKey: @"receipt"];
|
||||
[headers setObject: receipt forKey: @"Disposition-Notification-To"];
|
||||
}
|
||||
else
|
||||
[headers removeObjectForKey: @"Disposition-Notification-To"];
|
||||
{
|
||||
receipt = [newHeaders objectForKey: @"receipt"];
|
||||
if ([receipt isEqualToString: @"true"])
|
||||
{
|
||||
[headers setObject: receipt forKey: @"receipt"];
|
||||
pureSender = [[newHeaders objectForKey: @"from"] pureEMailAddress];
|
||||
if (pureSender)
|
||||
{
|
||||
[headers setObject: pureSender forKey: @"Disposition-Notification-To"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[headers removeObjectForKey: @"receipt"];
|
||||
[headers removeObjectForKey: @"Disposition-Notification-To"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *) headers
|
||||
@@ -799,8 +852,10 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
NSString *subject, *msgid;
|
||||
NSMutableDictionary *info;
|
||||
NSDictionary *h;
|
||||
NSMutableArray *addresses;
|
||||
NGImap4Envelope *sourceEnvelope;
|
||||
id priority, receipt;
|
||||
|
||||
[sourceMail fetchCoreInfos];
|
||||
|
||||
@@ -830,6 +885,15 @@ static NSString *userAgent = nil;
|
||||
[self _addEMailsOfAddresses: [sourceEnvelope replyTo] toArray: addresses];
|
||||
if ([addresses count] > 0)
|
||||
[info setObject: addresses forKey: @"replyTo"];
|
||||
|
||||
h = [sourceMail mailHeaders];
|
||||
priority = [h objectForKey: @"x-priority"];
|
||||
if ([priority isNotEmpty] && [priority isKindOfClass: [NSString class]])
|
||||
[info setObject: (NSString*)priority forKey: @"X-Priority"];
|
||||
receipt = [h objectForKey: @"disposition-notification-to"];
|
||||
if ([receipt isNotEmpty] && [receipt isKindOfClass: [NSString class]])
|
||||
[info setObject: (NSString*)receipt forKey: @"Disposition-Notification-To"];
|
||||
|
||||
[self setHeaders: info];
|
||||
|
||||
[self setText: [sourceMail contentForEditing]];
|
||||
@@ -1660,7 +1724,7 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
recipients = [headers objectForKey: fieldNames[count]];
|
||||
if ([recipients count] > 0)
|
||||
[allRecipients addObjectsFromArray: recipients];
|
||||
[allRecipients addObjectsFromArray: recipients];
|
||||
}
|
||||
|
||||
return allRecipients;
|
||||
@@ -1689,6 +1753,63 @@ static NSString *userAgent = nil;
|
||||
//
|
||||
- (NSException *) sendMail
|
||||
{
|
||||
SOGoUserDefaults *ud;
|
||||
ud = [[context activeUser] userDefaults];
|
||||
|
||||
if ([ud mailAddOutgoingAddresses])
|
||||
{
|
||||
SOGoContactFolders *contactFolders;
|
||||
NGMailAddressParser *parser;
|
||||
id parsedRecipient;
|
||||
SOGoContactFolder *folder;
|
||||
SOGoContactGCSEntry *newContact;
|
||||
NGVCard *card;
|
||||
Class contactGCSEntry;
|
||||
NSMutableArray *recipients;
|
||||
NSString *recipient, *emailAddress, *addressBook, *uid;
|
||||
NSArray *matchingContacts;
|
||||
int i;
|
||||
|
||||
// Get all the addressbooks
|
||||
contactFolders = [[[context activeUser] homeFolderInContext: context]
|
||||
lookupName: @"Contacts"
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
// Get all the recipients from the current email
|
||||
recipients = [self allRecipients];
|
||||
for (i = 0; i < [recipients count]; i++)
|
||||
{
|
||||
// The address contains a string. ex: "John Doe <sogo1@exemple.com>"
|
||||
recipient = [recipients objectAtIndex: i];
|
||||
parser = [NGMailAddressParser mailAddressParserWithString: recipient];
|
||||
parsedRecipient = [parser parse];
|
||||
emailAddress = [parsedRecipient address];
|
||||
|
||||
matchingContacts = [contactFolders allContactsFromFilter: emailAddress
|
||||
excludeGroups: YES
|
||||
excludeLists: YES];
|
||||
}
|
||||
// If we don't get any results from the autocompletion code, we add it..
|
||||
if ([matchingContacts count] == 0)
|
||||
{
|
||||
// Get the selected addressbook from the user preferences where the new address will be added
|
||||
addressBook = [ud selectedAddressBook];
|
||||
folder = [contactFolders lookupName: addressBook inContext: context acquire: NO];
|
||||
uid = [folder globallyUniqueObjectId];
|
||||
|
||||
if (folder && uid)
|
||||
{
|
||||
card = [NGVCard cardWithUid: uid];
|
||||
[card addEmail: emailAddress types: nil];
|
||||
|
||||
contactGCSEntry = NSClassFromString(@"SOGoContactGCSEntry");
|
||||
newContact = [contactGCSEntry objectWithName: uid
|
||||
inContainer: folder];
|
||||
[newContact setIsNew: YES];
|
||||
[newContact saveContentString: [card versitString]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [self sendMailAndCopyToSent: YES];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2009-2013 Inverse inc.
|
||||
Copyright (C) 2009-2014 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
@@ -71,6 +71,8 @@ typedef enum {
|
||||
|
||||
- (id) getInboxQuota;
|
||||
- (BOOL) updateFilters;
|
||||
- (BOOL) updateFiltersWithUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword;
|
||||
|
||||
- (NSArray *) identities;
|
||||
- (NSString *) signature;
|
||||
@@ -82,6 +84,8 @@ typedef enum {
|
||||
- (NSArray *) allFolderPaths;
|
||||
- (NSArray *) allFoldersMetadata;
|
||||
|
||||
- (NSDictionary *) imapFolderGUIDs;
|
||||
|
||||
- (BOOL) isInDraftsFolder;
|
||||
|
||||
/* special folders */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
Copyright (C) 2007-2013 Inverse inc.
|
||||
Copyright (C) 2007-2014 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -60,6 +60,8 @@
|
||||
#import "SOGoUser+Mailer.h"
|
||||
|
||||
#import "SOGoMailAccount.h"
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
|
||||
|
||||
#define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav"
|
||||
|
||||
@@ -306,12 +308,18 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
}
|
||||
|
||||
- (BOOL) updateFilters
|
||||
{
|
||||
return [self updateFiltersWithUsername: nil andPassword: nil];
|
||||
}
|
||||
|
||||
- (BOOL) updateFiltersWithUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword
|
||||
{
|
||||
SOGoSieveManager *manager;
|
||||
|
||||
manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]];
|
||||
|
||||
return [manager updateFiltersForAccount: self];
|
||||
return [manager updateFiltersForAccount: self withUsername: theUsername andPassword: thePassword];
|
||||
}
|
||||
|
||||
|
||||
@@ -653,6 +661,62 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
- (NSDictionary *) imapFolderGUIDs
|
||||
{
|
||||
NSDictionary *result, *nresult, *folderData;
|
||||
NSMutableDictionary *folders;
|
||||
NGImap4Client *client;
|
||||
SOGoUserDefaults *ud;
|
||||
NSArray *folderList;
|
||||
NSEnumerator *e;
|
||||
NSString *guid;
|
||||
id object;
|
||||
|
||||
BOOL subscribedOnly;
|
||||
|
||||
ud = [[context activeUser] userDefaults];
|
||||
subscribedOnly = [ud mailShowSubscribedFoldersOnly];
|
||||
|
||||
folderList = [[self imap4Connection] allFoldersForURL: [self imap4URL]
|
||||
onlySubscribedFolders: subscribedOnly];
|
||||
|
||||
folders = [NSMutableDictionary dictionary];
|
||||
|
||||
client = [[self imap4Connection] client];
|
||||
result = [client annotation: @"*" entryName: @"/comment" attributeName: @"value.priv"];
|
||||
|
||||
e = [folderList objectEnumerator];
|
||||
|
||||
while (object = [e nextObject])
|
||||
{
|
||||
guid = [[[[result objectForKey: @"FolderList"] objectForKey: [object substringFromIndex: 1]] objectForKey: @"/comment"] objectForKey: @"value.priv"];
|
||||
|
||||
if (!guid)
|
||||
{
|
||||
guid = [[NSProcessInfo processInfo] globallyUniqueString];
|
||||
nresult = [client annotation: [object substringFromIndex: 1] entryName: @"/comment" attributeName: @"value.priv" attributeValue: guid];
|
||||
|
||||
if (![[nresult objectForKey: @"result"] boolValue])
|
||||
{
|
||||
// Need to implement X-GUID query for Dovecot - this requires modification in SOPE to support following command:
|
||||
// 1 list "" "*" return (status (x-guid)) -> this would avoid firing a command per folder to IMAP
|
||||
nresult = [client status: [object substringFromIndex: 1] flags: [NSArray arrayWithObject: @"x-guid"]];
|
||||
guid = [nresult objectForKey: @"x-guid"];
|
||||
if (!guid)
|
||||
{
|
||||
guid = [NSString stringWithFormat: @"%@", [object substringFromIndex: 1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[folders setObject: guid forKey: [object substringFromIndex: 1]];
|
||||
}
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
|
||||
/* name lookup */
|
||||
|
||||
- (id) lookupName: (NSString *) _key
|
||||
|
||||
@@ -78,20 +78,23 @@
|
||||
{
|
||||
id obj;
|
||||
NSArray *accounts;
|
||||
NSString *key;
|
||||
SOGoUser *user;
|
||||
int keyCount;
|
||||
|
||||
key = [_key fromCSSIdentifier];
|
||||
|
||||
/* first check attributes directly bound to the application */
|
||||
obj = [super lookupName:_key inContext:_ctx acquire:NO];
|
||||
obj = [super lookupName:key inContext:_ctx acquire:NO];
|
||||
if (!obj)
|
||||
{
|
||||
user = [SOGoUser userWithLogin: [self ownerInContext: nil]];
|
||||
accounts = [user mailAccounts];
|
||||
|
||||
keyCount = [_key intValue];
|
||||
if ([_key isEqualToString: [NSString stringWithFormat: @"%d", keyCount]]
|
||||
keyCount = [key intValue];
|
||||
if ([key isEqualToString: [NSString stringWithFormat: @"%d", keyCount]]
|
||||
&& keyCount > -1 && keyCount < [accounts count])
|
||||
obj = [SOGoMailAccount objectWithName: _key inContainer: self];
|
||||
obj = [SOGoMailAccount objectWithName: key inContainer: self];
|
||||
else
|
||||
obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */];
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/*
|
||||
Copyright (C) 2007-2014 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of OpenGroupware.org.
|
||||
This file is part of SOGo.
|
||||
|
||||
OGo is free software; you can redistribute it and/or modify it under
|
||||
SOGo is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation; either version 2, or (at your option) any
|
||||
later version.
|
||||
|
||||
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
+5
@@ -13,6 +13,11 @@ from: WOString {
|
||||
escapeHTML = NO;
|
||||
}
|
||||
|
||||
newLine: WOString {
|
||||
value = newLine;
|
||||
escapeHTML = NO;
|
||||
}
|
||||
|
||||
hasReplyTo: WOConditional {
|
||||
condition = hasReplyTo;
|
||||
}
|
||||
|
||||
+5
@@ -22,6 +22,11 @@ from: WOString {
|
||||
escapeHTML = NO;
|
||||
}
|
||||
|
||||
newLine: WOString {
|
||||
value = newLine;
|
||||
escapeHTML = NO;
|
||||
}
|
||||
|
||||
hasReplyTo: WOConditional {
|
||||
condition = hasReplyTo;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2009-2011 Inverse inc.
|
||||
Copyright (C) 2009-2014 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo
|
||||
@@ -94,7 +94,6 @@
|
||||
|
||||
- (NSCalendarDate *) mostRecentMessageDate;
|
||||
|
||||
- (NSString *) davCollectionTagFromId: (NSString *) theId;
|
||||
- (NSString *) davCollectionTag;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
#import <NGImap4/NGImap4Connection.h>
|
||||
#import <NGImap4/NGImap4Client.h>
|
||||
#import <NGImap4/NSString+Imap4.h>
|
||||
|
||||
#import <SOGo/DOMNode+SOGo.h>
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
@@ -71,6 +72,28 @@
|
||||
|
||||
static NSString *defaultUserID = @"anyone";
|
||||
|
||||
static NSComparisonResult
|
||||
_compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
{
|
||||
static NSNumber *zeroNumber = nil;
|
||||
NSNumber *modseq1, *modseq2;
|
||||
|
||||
if (!zeroNumber)
|
||||
{
|
||||
zeroNumber = [NSNumber numberWithUnsignedLongLong: 0];
|
||||
[zeroNumber retain];
|
||||
}
|
||||
|
||||
modseq1 = [entry1 objectForKey: @"modseq"];
|
||||
if (!modseq1)
|
||||
modseq1 = zeroNumber;
|
||||
modseq2 = [entry2 objectForKey: @"modseq"];
|
||||
if (!modseq2)
|
||||
modseq2 = zeroNumber;
|
||||
|
||||
return [modseq1 compare: modseq2];
|
||||
}
|
||||
|
||||
@interface NGImap4Connection (PrivateMethods)
|
||||
|
||||
- (NSString *) imap4FolderNameForURL: (NSURL *) url;
|
||||
@@ -290,22 +313,30 @@ static NSString *defaultUserID = @"anyone";
|
||||
path = [[imap4URL path] stringByDeletingLastPathComponent];
|
||||
if (![path hasSuffix: @"/"])
|
||||
path = [path stringByAppendingString: @"/"];
|
||||
destURL = [[NSURL alloc] initWithScheme: [imap4URL scheme]
|
||||
host: [imap4URL host]
|
||||
path: [NSString stringWithFormat: @"%@%@",
|
||||
path, newName]];
|
||||
|
||||
// If new name contains the path - dont't need to add
|
||||
if ([newName rangeOfString: @"/"].location == NSNotFound)
|
||||
destURL = [[NSURL alloc] initWithScheme: [imap4URL scheme]
|
||||
host: [imap4URL host]
|
||||
path: [NSString stringWithFormat: @"%@%@",
|
||||
path, [newName stringByEncodingImap4FolderName]]];
|
||||
else
|
||||
destURL = [[NSURL alloc] initWithScheme: [imap4URL scheme]
|
||||
host: [imap4URL host]
|
||||
path: [NSString stringWithFormat: @"%@",
|
||||
[newName stringByEncodingImap4FolderName]]];
|
||||
[destURL autorelease];
|
||||
error = [imap4 moveMailboxAtURL: imap4URL
|
||||
toURL: destURL];
|
||||
if (!error)
|
||||
{
|
||||
ASSIGN (imap4URL, nil);
|
||||
ASSIGN (nameInContainer,
|
||||
([NSString stringWithFormat: @"folder%@", [newName asCSSIdentifier]]));
|
||||
|
||||
// We unsubscribe to the old one, and subscribe back to the new one
|
||||
[client subscribe: [destURL path]];
|
||||
[client unsubscribe: [imap4URL path]];
|
||||
|
||||
ASSIGN (imap4URL, nil);
|
||||
ASSIGN (nameInContainer,
|
||||
([NSString stringWithFormat: @"folder%@", [newName asCSSIdentifier]]));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -651,8 +682,12 @@ static NSString *defaultUserID = @"anyone";
|
||||
// Destination folder is in a different account
|
||||
SOGoMailAccounts *accounts;
|
||||
SOGoMailAccount *account;
|
||||
accounts = [[self container] container];
|
||||
SOGoUserFolder *userFolder;
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accounts = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
account = [accounts lookupName: [folders objectAtIndex: 1] inContext: localContext acquire: NO];
|
||||
|
||||
if ([account isKindOfClass: [NSException class]])
|
||||
{
|
||||
result = [NSException exceptionWithHTTPStatus: 500
|
||||
@@ -795,8 +830,9 @@ static NSString *defaultUserID = @"anyone";
|
||||
- (NSArray *) fetchUIDs: (NSArray *) _uids
|
||||
parts: (NSArray *) _parts
|
||||
{
|
||||
return [[self imap4Connection] fetchUIDs: _uids inURL: [self imap4URL]
|
||||
parts: _parts];
|
||||
return [[self imap4Connection] fetchUIDs: _uids
|
||||
inURL: [self imap4URL]
|
||||
parts: _parts];
|
||||
}
|
||||
|
||||
- (NSArray *) fetchUIDsOfVanishedItems: (uint64_t) modseq
|
||||
@@ -822,7 +858,7 @@ static NSString *defaultUserID = @"anyone";
|
||||
toFolderURL: [self imap4URL]];
|
||||
|
||||
return [NSException exceptionWithHTTPStatus: 502 /* Bad Gateway */
|
||||
reason: [NSString stringWithFormat: @"%@ is not an IMAP4 folder", [self relativeImap4Name]]];
|
||||
reason: [NSString stringWithFormat: @"%@ is not an IMAP4 folder", [self relativeImap4Name]]];
|
||||
}
|
||||
|
||||
- (NSException *) expunge
|
||||
@@ -994,7 +1030,7 @@ static NSString *defaultUserID = @"anyone";
|
||||
|
||||
if ([self imap4Connection])
|
||||
{
|
||||
error = [imap4 createMailbox: [self relativeImap4Name]
|
||||
error = [imap4 createMailbox: [[self relativeImap4Name] stringByEncodingImap4FolderName]
|
||||
atURL: [container imap4URL]];
|
||||
if (error)
|
||||
rc = NO;
|
||||
@@ -1939,8 +1975,11 @@ static NSString *defaultUserID = @"anyone";
|
||||
uid = [theId intValue];
|
||||
result = [[imap4 client] fetchModseqForUid: uid];
|
||||
modseq = [[[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"] intValue];
|
||||
|
||||
tag = [NSString stringWithFormat: @"%d-%d", uid, modseq-1];
|
||||
|
||||
if (modseq < 1)
|
||||
modseq = 1;
|
||||
|
||||
tag = [NSString stringWithFormat: @"%d-%d", uid, modseq];
|
||||
}
|
||||
|
||||
return tag;
|
||||
@@ -1997,16 +2036,45 @@ static NSString *defaultUserID = @"anyone";
|
||||
//
|
||||
// Check updated items
|
||||
//
|
||||
// . UID FETCH 1:* (UID) (CHANGEDSINCE 1)
|
||||
// * 1 FETCH (UID 542 MODSEQ (7))
|
||||
// * 2 FETCH (UID 553 MODSEQ (14))
|
||||
// * 3 FETCH (UID 554 MODSEQ (16))
|
||||
// * 4 FETCH (UID 555 MODSEQ (15))
|
||||
// * 5 FETCH (UID 559 MODSEQ (17))
|
||||
// * 6 FETCH (UID 560 MODSEQ (18))
|
||||
// * 7 FETCH (UID 561 MODSEQ (19))
|
||||
//
|
||||
// . uid fetch 1:* (FLAGS) (changedsince 171)
|
||||
// SORT + MODSEQ: http://www.watersprings.org/pub/id/draft-melnikov-condstore-sort-00.txt
|
||||
// With date, not modseq
|
||||
// . UID SORT (DATE) UTF-8 (NOT DELETED) (SINCE "15-Mar-2014")
|
||||
// * SORT 553 542 555 554 601 559 560 561 565 602 603 605 611 610 612 613 614 615 616 617 618 621 619 620 622 623
|
||||
//
|
||||
// . UID SORT (DATE) UTF-8 ((MODSEQ 64) (NOT DELETED)) (SINCE "15-Mar-2014")
|
||||
// * SORT 623 624 (MODSEQ 65)
|
||||
// . OK Completed (2 msgs in 0.000 secs)
|
||||
//
|
||||
// ".. the server MUST also append (to the end of the untagged SORT response) the highest mod-sequence for all messages being returned."
|
||||
//
|
||||
// To get the modseq of a specific message:
|
||||
//
|
||||
// . uid fetch 124569:124569 uid (changedsince 1)
|
||||
// . UID FETCH 124569:124569 (UID MODSEQ)
|
||||
// * 4900 FETCH (UID 124569 MODSEQ (2))
|
||||
//
|
||||
//
|
||||
// Deleted: "UID FETCH 1:* (UID) (CHANGEDSINCE 171 VANISHED)"
|
||||
// To get deleted messages
|
||||
//
|
||||
// . UID FETCH 1:* (UID) (CHANGEDSINCE 1 VANISHED)
|
||||
// * VANISHED (EARLIER) 1:541,543:552,556:558,562:564,566:600,604,606:609
|
||||
// * 1 FETCH (UID 542 MODSEQ (7))
|
||||
// * 2 FETCH (UID 553 MODSEQ (14))
|
||||
// * 3 FETCH (UID 554 MODSEQ (16))
|
||||
// * 4 FETCH (UID 555 MODSEQ (15))
|
||||
// * 5 FETCH (UID 559 MODSEQ (17))
|
||||
// * 6 FETCH (UID 560 MODSEQ (18))
|
||||
// * 7 FETCH (UID 561 MODSEQ (19))
|
||||
|
||||
//
|
||||
// fetchUIDsOfVanishedItems ..
|
||||
//
|
||||
// . uid fetch 1:* (FLAGS) (changedsince 176 vanished)
|
||||
@@ -2023,6 +2091,7 @@ static NSString *defaultUserID = @"anyone";
|
||||
NSMutableArray *allTokens;
|
||||
NSArray *a, *uids;
|
||||
NSDictionary *d;
|
||||
id fetchResults;
|
||||
|
||||
int uidnext, highestmodseq, i;
|
||||
|
||||
@@ -2049,7 +2118,7 @@ static NSString *defaultUserID = @"anyone";
|
||||
EOKeyValueQualifier *kvQualifier;
|
||||
NSNumber *nextModseq;
|
||||
|
||||
nextModseq = [NSNumber numberWithUnsignedLongLong: highestmodseq + 1];
|
||||
nextModseq = [NSNumber numberWithUnsignedLongLong: highestmodseq];
|
||||
kvQualifier = [[EOKeyValueQualifier alloc]
|
||||
initWithKey: @"modseq"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
@@ -2067,9 +2136,10 @@ static NSString *defaultUserID = @"anyone";
|
||||
|
||||
if (theStartDate)
|
||||
{
|
||||
EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"(DATE >= %@)", theStartDate];
|
||||
EOQualifier *sinceDateQualifier;
|
||||
|
||||
sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"(DATE >= %@)", theStartDate];
|
||||
searchQualifier = [[EOAndQualifier alloc] initWithQualifiers: searchQualifier, sinceDateQualifier,
|
||||
nil];
|
||||
[searchQualifier autorelease];
|
||||
@@ -2080,31 +2150,38 @@ static NSString *defaultUserID = @"anyone";
|
||||
uids = [self fetchUIDsMatchingQualifier: searchQualifier
|
||||
sortOrdering: nil];
|
||||
|
||||
for (i = 0; i < [uids count]; i++)
|
||||
{
|
||||
// New messages
|
||||
if ([[uids objectAtIndex: i] intValue] >= uidnext)
|
||||
{
|
||||
d = [NSDictionary dictionaryWithObject: @"added" forKey: [uids objectAtIndex: i]];
|
||||
}
|
||||
// Changed messages
|
||||
else
|
||||
{
|
||||
d = [NSDictionary dictionaryWithObject: @"changed" forKey: [uids objectAtIndex: i]];
|
||||
}
|
||||
fetchResults = [(NSDictionary *)[self fetchUIDs: uids
|
||||
parts: [NSArray arrayWithObject: @"modseq"]]
|
||||
objectForKey: @"fetch"];
|
||||
|
||||
/* NOTE: we sort items manually because Cyrus does not properly sort
|
||||
entries with a MODSEQ of 0 */
|
||||
fetchResults
|
||||
= [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
|
||||
context: NULL];
|
||||
|
||||
for (i = 0; i < [fetchResults count]; i++)
|
||||
{
|
||||
d = [NSDictionary dictionaryWithObject: [[fetchResults objectAtIndex: i] objectForKey: @"modseq"]
|
||||
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
|
||||
[allTokens addObject: d];
|
||||
}
|
||||
|
||||
|
||||
// We fetch deleted ones
|
||||
if (highestmodseq == 0)
|
||||
highestmodseq = 1;
|
||||
|
||||
if (highestmodseq > 0)
|
||||
{
|
||||
id uid;
|
||||
|
||||
uids = [self fetchUIDsOfVanishedItems: highestmodseq];
|
||||
|
||||
for (i = 0; i < [uids count]; i++)
|
||||
{
|
||||
d = [NSDictionary dictionaryWithObject: @"deleted" forKey: [uids objectAtIndex: i]];
|
||||
uid = [[uids objectAtIndex: i] stringValue];
|
||||
d = [NSDictionary dictionaryWithObject: [NSNull null] forKey: uid];
|
||||
[allTokens addObject: d];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#import "../../UI/SOGoUI/UIxComponent.h";
|
||||
#import "../../UI/SOGoUI/UIxComponent.h"
|
||||
|
||||
@interface SOGoMailLabel : NSObject
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2009 Inverse inc.
|
||||
Copyright (C) 2007-2014 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
@@ -434,7 +434,8 @@ static BOOL debugSoParts = NO;
|
||||
NSData *content;
|
||||
id result, fullResult;
|
||||
|
||||
fullResult = [self fetchParts: [NSArray arrayWithObject: @"RFC822"]];
|
||||
// We avoid using RFC822 here as the part name as it'll flag the message as Seen
|
||||
fullResult = [self fetchParts: [NSArray arrayWithObject: @"BODY.PEEK[]"]];
|
||||
if (fullResult == nil)
|
||||
return nil;
|
||||
|
||||
@@ -458,7 +459,7 @@ static BOOL debugSoParts = NO;
|
||||
|
||||
/* extract message */
|
||||
|
||||
if ((content = [result valueForKey: @"message"]) == nil) {
|
||||
if ((content = [[result valueForKey: @"body[]"] valueForKey: @"data"]) == nil) {
|
||||
[self logWithFormat:
|
||||
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
|
||||
result];
|
||||
@@ -504,26 +505,6 @@ static BOOL debugSoParts = NO;
|
||||
}
|
||||
|
||||
/* bulk fetching of plain/text content */
|
||||
|
||||
// - (BOOL) shouldFetchPartOfType: (NSString *) _type
|
||||
// subtype: (NSString *) _subtype
|
||||
// {
|
||||
// /*
|
||||
// This method decides which parts are 'prefetched' for display. Those are
|
||||
// usually text parts (the set is currently hardcoded in this method ...).
|
||||
// */
|
||||
// _type = [_type lowercaseString];
|
||||
// _subtype = [_subtype lowercaseString];
|
||||
|
||||
// return (([_type isEqualToString: @"text"]
|
||||
// && ([_subtype isEqualToString: @"plain"]
|
||||
// || [_subtype isEqualToString: @"html"]
|
||||
// || [_subtype isEqualToString: @"calendar"]))
|
||||
// || ([_type isEqualToString: @"application"]
|
||||
// && ([_subtype isEqualToString: @"pgp-signature"]
|
||||
// || [_subtype hasPrefix: @"x-vnd.kolab."])));
|
||||
// }
|
||||
|
||||
- (void) addRequiredKeysOfStructure: (NSDictionary *) info
|
||||
path: (NSString *) p
|
||||
toArray: (NSMutableArray *) keys
|
||||
@@ -623,9 +604,12 @@ static BOOL debugSoParts = NO;
|
||||
@"text/calendar", @"application/ics",
|
||||
@"application/pgp-signature", nil];
|
||||
ma = [NSMutableArray arrayWithCapacity: 4];
|
||||
|
||||
[self addRequiredKeysOfStructure: [self bodyStructure]
|
||||
path: @"" toArray: ma acceptedTypes: types
|
||||
withPeek: NO];
|
||||
path: @""
|
||||
toArray: ma
|
||||
acceptedTypes: types
|
||||
withPeek: YES];
|
||||
|
||||
return ma;
|
||||
}
|
||||
@@ -655,6 +639,12 @@ static BOOL debugSoParts = NO;
|
||||
NSData *data;
|
||||
|
||||
key = [[_fetchKeys objectAtIndex:i] objectForKey: @"key"];
|
||||
|
||||
// We'll ask for the body.peek[] but SOPE returns us body[] responses
|
||||
// so the key won't ever be found.
|
||||
if ([key hasPrefix: @"body.peek["])
|
||||
key = [NSString stringWithFormat: @"body[%@", [key substringFromIndex: 10]];
|
||||
|
||||
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
|
||||
objectForKey: @"data"];
|
||||
|
||||
@@ -780,6 +770,9 @@ static BOOL debugSoParts = NO;
|
||||
[mimeType hasPrefix: @"image/"] ||
|
||||
[mimeType hasPrefix: @"video/"])
|
||||
filename = [NSString stringWithFormat: @"unknown_%@", path];
|
||||
else if ([mimeType isEqualToString: @"message/rfc822"])
|
||||
filename = [NSString stringWithFormat: @"email_%@.eml", path];
|
||||
|
||||
|
||||
if (filename)
|
||||
{
|
||||
@@ -1345,11 +1338,6 @@ static BOOL debugSoParts = NO;
|
||||
return mailETag;
|
||||
}
|
||||
|
||||
- (int) zlGenerationCount
|
||||
{
|
||||
return 0; /* mails never change */
|
||||
}
|
||||
|
||||
- (NSArray *) aclsForUser: (NSString *) uid
|
||||
{
|
||||
return [container aclsForUser: uid];
|
||||
@@ -1533,7 +1521,7 @@ static BOOL debugSoParts = NO;
|
||||
NSRange range;
|
||||
|
||||
rc = nil;
|
||||
fetch = [self _fetchProperty: @"BODY[HEADER.FIELDS (RECEIVED)]"];
|
||||
fetch = [self _fetchProperty: @"BODY.PEEK[HEADER.FIELDS (RECEIVED)]"];
|
||||
|
||||
if ([fetch count])
|
||||
{
|
||||
@@ -1568,7 +1556,7 @@ static BOOL debugSoParts = NO;
|
||||
NSString *value, *rc;
|
||||
|
||||
rc = nil;
|
||||
fetch = [self _fetchProperty: @"BODY[HEADER.FIELDS (REFERENCES)]"];
|
||||
fetch = [self _fetchProperty: @"BODY.PEEK[HEADER.FIELDS (REFERENCES)]"];
|
||||
|
||||
if ([fetch count])
|
||||
{
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Ostatní užívatelia";
|
||||
"SharedFoldersName" = "Zdielané adresáre";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Otros usuarios";
|
||||
"SharedFoldersName" = "Carpetas compartidas";
|
||||
@@ -0,0 +1,2 @@
|
||||
"OtherUsersFolderName" = "Otros usuarios";
|
||||
"SharedFoldersName" = "Carpetas compartidas";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Filter";
|
||||
"OtherUsersFolderName" = "Other Users";
|
||||
@@ -0,0 +1,2 @@
|
||||
"SieveFolderName" = "Теки";
|
||||
"OtherUsersFolderName" = "Інші користувачі";
|
||||
Loaded 100 of 338 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user