Merge branch 'master' into feature/addMissingStrings2
Conflicts: UI/AdministrationUI/English.lproj/Localizable.strings UI/PreferencesUI/English.lproj/Localizable.strings UI/Templates/PreferencesUI/UIxFilterEditor.woxpull/101/head
commit
9b0a2c5fae
|
@ -9,6 +9,7 @@
|
||||||
*/*/obj/
|
*/*/obj/
|
||||||
*/obj/
|
*/obj/
|
||||||
.scss-lint-config.yml_
|
.scss-lint-config.yml_
|
||||||
|
ActiveSync/ActiveSync.SOGo
|
||||||
Documentation/*.docbook
|
Documentation/*.docbook
|
||||||
Documentation/*.pdf
|
Documentation/*.pdf
|
||||||
SoObjects/SOGo/SOGo.framework/
|
SoObjects/SOGo/SOGo.framework/
|
||||||
|
@ -20,11 +21,8 @@ UI/WebServerResources/bower_components/
|
||||||
UI/WebServerResources/css/
|
UI/WebServerResources/css/
|
||||||
UI/WebServerResources/css/styles.css
|
UI/WebServerResources/css/styles.css
|
||||||
UI/WebServerResources/css/styles.css.map
|
UI/WebServerResources/css/styles.css.map
|
||||||
UI/WebServerResources/js/Common.js*
|
UI/WebServerResources/js/*.js
|
||||||
UI/WebServerResources/js/Contacts.js*
|
UI/WebServerResources/js/*.js.map
|
||||||
UI/WebServerResources/js/Mailer.js*
|
|
||||||
UI/WebServerResources/js/Preferences.js*
|
|
||||||
UI/WebServerResources/js/Scheduler.js*
|
|
||||||
UI/WebServerResources/js/vendor/
|
UI/WebServerResources/js/vendor/
|
||||||
UI/WebServerResources/node_modules/
|
UI/WebServerResources/node_modules/
|
||||||
UI/WebServerResources/scss/.sass-cache/
|
UI/WebServerResources/scss/.sass-cache/
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
# compilation settings
|
ifeq ($(HAS_LIBRARY_ssl),yes)
|
||||||
|
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
|
||||||
|
BUNDLE_LIBS += -lcrypto
|
||||||
|
endif
|
||||||
|
|
|
@ -101,7 +101,7 @@ static NSArray *asElementArray = nil;
|
||||||
int i, count;
|
int i, count;
|
||||||
|
|
||||||
if (!asElementArray)
|
if (!asElementArray)
|
||||||
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil];
|
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
|
||||||
|
|
||||||
data = [NSMutableDictionary dictionary];
|
data = [NSMutableDictionary dictionary];
|
||||||
|
|
||||||
|
|
|
@ -48,11 +48,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
@implementation NGVCard (ActiveSync)
|
@implementation NGVCard (ActiveSync)
|
||||||
|
|
||||||
|
//
|
||||||
|
// This function is called for each elements which can be ghosted according to specs.
|
||||||
|
// https://msdn.microsoft.com/en-us/library/gg650908%28v=exchg.80%29.aspx
|
||||||
|
//
|
||||||
|
- (BOOL) _isGhosted: (NSString *) element
|
||||||
|
inContext: (WOContext *) context
|
||||||
|
{
|
||||||
|
NSArray *supportedElements;
|
||||||
|
|
||||||
|
supportedElements = [context objectForKey: @"SupportedElements"];
|
||||||
|
|
||||||
|
// If the client does not include a Supported element in the initial Sync command request for
|
||||||
|
// a folder, then all of the elements that can be ghosted are considered not ghosted.
|
||||||
|
if (!supportedElements)
|
||||||
|
return NO;
|
||||||
|
|
||||||
|
// If the client includes an empty Supported element in the initial Sync command request for
|
||||||
|
// a folder, then all elements that can be ghosted are considered ghosted.
|
||||||
|
if (![supportedElements count])
|
||||||
|
return YES;
|
||||||
|
|
||||||
|
// If the client includes a Supported element that contains child elements in the initial
|
||||||
|
// Sync command request for a folder, then each child element of that Supported element is
|
||||||
|
// considered not ghosted. All elements that can be ghosted that are not included as child
|
||||||
|
// elements of the Supported element are considered ghosted.
|
||||||
|
if (!([supportedElements indexOfObject: element] == NSNotFound))
|
||||||
|
return YES;
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
||||||
{
|
{
|
||||||
NSArray *emails, *addresses, *categories, *elements;
|
NSArray *emails, *addresses, *categories, *elements;
|
||||||
CardElement *n, *homeAdr, *workAdr;
|
CardElement *n, *homeAdr, *workAdr;
|
||||||
NSMutableString *s;
|
NSMutableString *s, *a;
|
||||||
NSString *url;
|
NSString *url;
|
||||||
id o;
|
id o;
|
||||||
|
|
||||||
|
@ -63,7 +94,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
if ((o = [n flattenedValueAtIndex: 0 forKey: @""]))
|
if ((o = [n flattenedValueAtIndex: 0 forKey: @""]))
|
||||||
[s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
|
[s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
|
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
|
||||||
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
|
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
|
@ -146,16 +177,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
if ([addresses count])
|
if ([addresses count])
|
||||||
{
|
{
|
||||||
homeAdr = [addresses objectAtIndex: 0];
|
homeAdr = [addresses objectAtIndex: 0];
|
||||||
|
a = [NSMutableString string];
|
||||||
|
|
||||||
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""]))
|
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""]))
|
||||||
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [o activeSyncRepresentationInContext: context]];
|
[a appendString: o];
|
||||||
|
|
||||||
|
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||||
|
[a appendFormat: @"\n%@", o];
|
||||||
|
|
||||||
|
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
|
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||||
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
|
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""]))
|
if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""]))
|
||||||
[s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]];
|
[s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""]))
|
if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""]))
|
||||||
[s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]];
|
[s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
|
@ -171,9 +208,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
if ([addresses count])
|
if ([addresses count])
|
||||||
{
|
{
|
||||||
workAdr = [addresses objectAtIndex: 0];
|
workAdr = [addresses objectAtIndex: 0];
|
||||||
|
a = [NSMutableString string];
|
||||||
|
|
||||||
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""]))
|
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""]))
|
||||||
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [o activeSyncRepresentationInContext: context]];
|
[a appendString: o];
|
||||||
|
|
||||||
|
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||||
|
[a appendFormat: @"\n%@", o];
|
||||||
|
|
||||||
|
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
|
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||||
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
|
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
|
||||||
|
@ -217,6 +260,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
inContext: (WOContext *) context
|
inContext: (WOContext *) context
|
||||||
{
|
{
|
||||||
CardElement *element;
|
CardElement *element;
|
||||||
|
NSMutableArray *addressLines;
|
||||||
id o;
|
id o;
|
||||||
|
|
||||||
// Contact's note
|
// Contact's note
|
||||||
|
@ -226,6 +270,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// Categories
|
// Categories
|
||||||
if ((o = [theValues objectForKey: @"Categories"]) && [o length])
|
if ((o = [theValues objectForKey: @"Categories"]) && [o length])
|
||||||
[self setCategories: o];
|
[self setCategories: o];
|
||||||
|
else
|
||||||
|
[[self children] removeObjectsInArray: [self childrenWithTag: @"Categories"]];
|
||||||
|
|
||||||
// Birthday
|
// Birthday
|
||||||
if ((o = [theValues objectForKey: @"Birthday"]))
|
if ((o = [theValues objectForKey: @"Birthday"]))
|
||||||
|
@ -233,6 +279,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
o = [o calendarDate];
|
o = [o calendarDate];
|
||||||
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
|
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
|
||||||
}
|
}
|
||||||
|
else if (![self _isGhosted: @"Birthday" inContext: context])
|
||||||
|
{
|
||||||
|
[self setBday: @""];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Business address information
|
// Business address information
|
||||||
|
@ -244,18 +295,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// BusinessCountry
|
// BusinessCountry
|
||||||
//
|
//
|
||||||
element = [self elementWithTag: @"adr" ofType: @"work"];
|
element = [self elementWithTag: @"adr" ofType: @"work"];
|
||||||
[element setSingleValue: @""
|
|
||||||
atIndex: 1 forKey: @""];
|
if ((o = [theValues objectForKey: @"BusinessStreet"]) || ![self _isGhosted: @"BusinessStreet" inContext: context])
|
||||||
[element setSingleValue: [theValues objectForKey: @"BusinessStreet"]
|
{
|
||||||
atIndex: 2 forKey: @""];
|
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
|
||||||
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
|
|
||||||
atIndex: 3 forKey: @""];
|
[element setSingleValue: @""
|
||||||
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
|
atIndex: 1 forKey: @""];
|
||||||
atIndex: 4 forKey: @""];
|
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
|
||||||
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
|
atIndex: 2 forKey: @""];
|
||||||
atIndex: 5 forKey: @""];
|
|
||||||
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
|
// Extended address line. If there are more than 2 address lines we add them to the extended address line.
|
||||||
atIndex: 6 forKey: @""];
|
if ([addressLines count] > 1)
|
||||||
|
{
|
||||||
|
[addressLines removeObjectAtIndex: 0];
|
||||||
|
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
|
||||||
|
atIndex: 1 forKey: @""];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"BusinessCity"]) || ![self _isGhosted: @"BusinessCity" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
|
||||||
|
atIndex: 3 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"BusinessState"]) || ![self _isGhosted: @"BusinessState" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
|
||||||
|
atIndex: 4 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"BusinessPostalCode"]) || ![self _isGhosted: @"BusinessPostalCode" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
|
||||||
|
atIndex: 5 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"BusinessCountry"]) || ![self _isGhosted: @"BusinessCountry" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
|
||||||
|
atIndex: 6 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Home address information
|
// Home address information
|
||||||
|
@ -267,35 +348,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// HomeCountry
|
// HomeCountry
|
||||||
//
|
//
|
||||||
element = [self elementWithTag: @"adr" ofType: @"home"];
|
element = [self elementWithTag: @"adr" ofType: @"home"];
|
||||||
[element setSingleValue: @""
|
|
||||||
atIndex: 1 forKey: @""];
|
if ((o = [theValues objectForKey: @"HomeStreet"]) || ![self _isGhosted: @"HomeStreet" inContext: context])
|
||||||
[element setSingleValue: [theValues objectForKey: @"HomeStreet"]
|
{
|
||||||
atIndex: 2 forKey: @""];
|
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
|
||||||
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
|
|
||||||
atIndex: 3 forKey: @""];
|
[element setSingleValue: @""
|
||||||
[element setSingleValue: [theValues objectForKey: @"HomeState"]
|
atIndex: 1 forKey: @""];
|
||||||
atIndex: 4 forKey: @""];
|
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
|
||||||
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
|
atIndex: 2 forKey: @""];
|
||||||
atIndex: 5 forKey: @""];
|
|
||||||
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
|
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
|
||||||
atIndex: 6 forKey: @""];
|
if ([addressLines count] > 1)
|
||||||
|
{
|
||||||
|
[addressLines removeObjectAtIndex: 0];
|
||||||
|
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
|
||||||
|
atIndex: 1 forKey: @""];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"HomeCity"]) || ![self _isGhosted: @"HomeCity" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
|
||||||
|
atIndex: 3 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"HomeState"]) || ![self _isGhosted: @"HomeState" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"HomeState"]
|
||||||
|
atIndex: 4 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"HomePostalCode"]) || ![self _isGhosted: @"HomePostalCode" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
|
||||||
|
atIndex: 5 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((o = [theValues objectForKey: @"HomeCountry"]) || ![self _isGhosted: @"HomeCountry" inContext: context])
|
||||||
|
{
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
|
||||||
|
atIndex: 6 forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
// Company's name
|
// Company's name
|
||||||
if ((o = [theValues objectForKey: @"CompanyName"]))
|
if ((o = [theValues objectForKey: @"CompanyName"]))
|
||||||
[self setOrg: o units: nil];
|
[self setOrg: o units: nil];
|
||||||
|
else if (![self _isGhosted: @"CompanyName" inContext: context])
|
||||||
|
[self setOrg: @"" units: nil];
|
||||||
|
|
||||||
// Department
|
// Department
|
||||||
if ((o = [theValues objectForKey: @"Department"]))
|
if ((o = [theValues objectForKey: @"Department"]))
|
||||||
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
|
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
|
||||||
|
else if (![self _isGhosted: @"Department" inContext: context])
|
||||||
|
[self setOrg: nil units: [NSArray arrayWithObjects:@"",nil]];
|
||||||
|
|
||||||
// Email addresses
|
// Email addresses
|
||||||
if ((o = [theValues objectForKey: @"Email1Address"]))
|
if ((o = [theValues objectForKey: @"Email1Address"]) || ![self _isGhosted: @"Email1Address" inContext: context])
|
||||||
{
|
{
|
||||||
element = [self elementWithTag: @"email" ofType: @"work"];
|
element = [self elementWithTag: @"email" ofType: @"work"];
|
||||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((o = [theValues objectForKey: @"Email2Address"]))
|
if ((o = [theValues objectForKey: @"Email2Address"]) || ![self _isGhosted: @"Email2Address" inContext: context])
|
||||||
{
|
{
|
||||||
element = [self elementWithTag: @"email" ofType: @"home"];
|
element = [self elementWithTag: @"email" ofType: @"home"];
|
||||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||||
|
@ -303,7 +418,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
|
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
|
||||||
// FIXME: revise this when the GUI revamp is done in SOGo
|
// FIXME: revise this when the GUI revamp is done in SOGo
|
||||||
if ((o = [theValues objectForKey: @"Email3Address"]))
|
if ((o = [theValues objectForKey: @"Email3Address"]) || ![self _isGhosted: @"Email3Address" inContext: context])
|
||||||
{
|
{
|
||||||
element = [self elementWithTag: @"email" ofType: @"three"];
|
element = [self elementWithTag: @"email" ofType: @"three"];
|
||||||
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
[element setSingleValue: [o pureEMailAddress] forKey: @""];
|
||||||
|
@ -313,45 +428,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// MiddleName
|
// MiddleName
|
||||||
// Suffix (II)
|
// Suffix (II)
|
||||||
// Title (Mr.)
|
// Title (Mr.)
|
||||||
[self setFn: [theValues objectForKey: @"FileAs"]];
|
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
|
||||||
|
[self setFn: [theValues objectForKey: @"FileAs"]];
|
||||||
|
|
||||||
[self setNWithFamily: [theValues objectForKey: @"LastName"]
|
[self setNWithFamily: [theValues objectForKey: @"LastName"]
|
||||||
given: [theValues objectForKey: @"FirstName"]
|
given: [theValues objectForKey: @"FirstName"]
|
||||||
additional: nil prefixes: nil suffixes: nil];
|
additional: nil prefixes: nil suffixes: nil];
|
||||||
|
|
||||||
// IM information
|
// IM information
|
||||||
[[self uniqueChildWithTag: @"x-aim"]
|
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
|
||||||
setSingleValue: [theValues objectForKey: @"IMAddress"]
|
[[self uniqueChildWithTag: @"x-aim"]
|
||||||
forKey: @""];
|
setSingleValue: [theValues objectForKey: @"IMAddress"]
|
||||||
|
forKey: @""];
|
||||||
|
|
||||||
//
|
//
|
||||||
// Phone numbrrs
|
// Phone numbrrs
|
||||||
//
|
//
|
||||||
element = [self elementWithTag: @"tel" ofType: @"work"];
|
if ((o = [theValues objectForKey: @"BusinessPhoneNumber"]) || ![self _isGhosted: @"BusinessPhoneNumber" inContext: context])
|
||||||
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
|
{
|
||||||
|
element = [self elementWithTag: @"tel" ofType: @"work"];
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
element = [self elementWithTag: @"tel" ofType: @"home"];
|
if ((o = [theValues objectForKey: @"HomePhoneNumber"]) || ![self _isGhosted: @"HomePhoneNumber" inContext: context])
|
||||||
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
|
{
|
||||||
|
element = [self elementWithTag: @"tel" ofType: @"home"];
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
element = [self elementWithTag: @"tel" ofType: @"cell"];
|
if ((o = [theValues objectForKey: @"MobilePhoneNumber"]) || ![self _isGhosted: @"MobilePhoneNumber" inContext: context])
|
||||||
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
|
{
|
||||||
|
element = [self elementWithTag: @"tel" ofType: @"cell"];
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
element = [self elementWithTag: @"tel" ofType: @"fax"];
|
if ((o = [theValues objectForKey: @"BusinessFaxNumber"]) || ![self _isGhosted: @"BusinessFaxNumber" inContext: context])
|
||||||
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
|
{
|
||||||
|
element = [self elementWithTag: @"tel" ofType: @"fax"];
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
element = [self elementWithTag: @"tel" ofType: @"pager"];
|
if ((o = [theValues objectForKey: @"PagerNumber"]) || ![self _isGhosted: @"PagerNumber" inContext: context])
|
||||||
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
|
{
|
||||||
|
element = [self elementWithTag: @"tel" ofType: @"pager"];
|
||||||
|
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
|
||||||
|
}
|
||||||
|
|
||||||
// Job's title
|
// Job's title
|
||||||
if ((o = [theValues objectForKey: @"JobTitle"]))
|
if ((o = [theValues objectForKey: @"JobTitle"]) || ![self _isGhosted: @"JobTitle" inContext: context])
|
||||||
[self setTitle: o];
|
[self setTitle: o];
|
||||||
|
|
||||||
// WebPage (work)
|
// WebPage (work)
|
||||||
if ((o = [theValues objectForKey: @"WebPage"]))
|
if ((o = [theValues objectForKey: @"WebPage"]) || ![self _isGhosted: @"WebPage" inContext: context])
|
||||||
[[self elementWithTag: @"url" ofType: @"work"]
|
[[self elementWithTag: @"url" ofType: @"work"]
|
||||||
setSingleValue: o forKey: @""];
|
setSingleValue: o forKey: @""];
|
||||||
|
|
||||||
if ((o = [theValues objectForKey: @"NickName"]))
|
if ((o = [theValues objectForKey: @"NickName"]) || ![self _isGhosted: @"NickName" inContext: context])
|
||||||
[self setNickname: o];
|
[self setNickname: o];
|
||||||
|
|
||||||
if ((o = [theValues objectForKey: @"Picture"]))
|
if ((o = [theValues objectForKey: @"Picture"]))
|
||||||
|
|
|
@ -112,13 +112,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
@implementation SOGoActiveSyncDispatcher (Sync)
|
@implementation SOGoActiveSyncDispatcher (Sync)
|
||||||
|
|
||||||
|
- (void) _setOrUnsetSyncRequest: (BOOL) set
|
||||||
|
collections: (NSArray *) collections
|
||||||
|
{
|
||||||
|
SOGoCacheGCSObject *o;
|
||||||
|
NSNumber *processIdentifier;
|
||||||
|
NSString *key;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||||
|
|
||||||
|
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||||
|
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||||
|
[o setTableUrl: [self folderTableURL]];
|
||||||
|
[o reloadIfNeeded];
|
||||||
|
|
||||||
|
if (set)
|
||||||
|
{
|
||||||
|
RELEASE(syncRequest);
|
||||||
|
syncRequest = [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]];
|
||||||
|
RETAIN(syncRequest);
|
||||||
|
|
||||||
|
[[o properties] setObject: syncRequest forKey: @"SyncRequest"];
|
||||||
|
|
||||||
|
for (i = 0; i < [collections count]; i++)
|
||||||
|
{
|
||||||
|
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||||
|
[[o properties] setObject: processIdentifier forKey: key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[[o properties] removeObjectForKey: @"SyncRequest"];
|
||||||
|
for (i = 0; i < [collections count]; i++)
|
||||||
|
{
|
||||||
|
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||||
|
[[o properties] removeObjectForKey: key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[o save];
|
||||||
|
}
|
||||||
|
|
||||||
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
|
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
|
||||||
forKey: (NSString *) theFolderKey
|
forKey: (NSString *) theFolderKey
|
||||||
{
|
{
|
||||||
|
NSNumber *processIdentifier, *processIdentifierInCache;
|
||||||
SOGoCacheGCSObject *o;
|
SOGoCacheGCSObject *o;
|
||||||
NSDictionary *values;
|
NSDictionary *values;
|
||||||
NSString *key;
|
NSString *key;
|
||||||
|
|
||||||
|
if ([theFolderKey hasPrefix: @"folder"])
|
||||||
|
key = [NSString stringWithFormat: @"SyncRequest+mail/%@", [theFolderKey substringFromIndex: 6]];
|
||||||
|
else
|
||||||
|
key = [NSString stringWithFormat: @"SyncRequest+%@", theFolderKey];
|
||||||
|
|
||||||
|
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||||
|
processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key];
|
||||||
|
|
||||||
|
// Don't update the cache if another request is processing the same collection.
|
||||||
|
if (!([processIdentifierInCache isEqual: processIdentifier]))
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - We lost our lock - discard folder cache update %@ %@ <> %@", key, processIdentifierInCache, processIdentifier];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
||||||
values = [theFolderMetadata copy];
|
values = [theFolderMetadata copy];
|
||||||
|
|
||||||
|
@ -132,7 +192,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[[o properties] removeObjectForKey: @"DateCache"];
|
[[o properties] removeObjectForKey: @"DateCache"];
|
||||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||||
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||||
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||||
|
|
||||||
[[o properties] addEntriesFromDictionary: values];
|
[[o properties] addEntriesFromDictionary: values];
|
||||||
[o save];
|
[o save];
|
||||||
|
@ -159,7 +221,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
NSString *nameInCache;
|
NSString *nameInCache;
|
||||||
|
|
||||||
if (theFolderType == ActiveSyncMailFolder)
|
if (theFolderType == ActiveSyncMailFolder)
|
||||||
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]];
|
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NSString *component_name;
|
NSString *component_name;
|
||||||
|
@ -170,7 +232,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
else
|
else
|
||||||
component_name = @"vtodo";
|
component_name = @"vtodo";
|
||||||
|
|
||||||
nameInCache= [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return nameInCache;
|
return nameInCache;
|
||||||
|
@ -403,8 +465,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[o takeActiveSyncValues: allChanges inContext: context];
|
[o takeActiveSyncValues: allChanges inContext: context];
|
||||||
[sogoObject saveComponent: o];
|
[sogoObject saveComponent: o];
|
||||||
|
|
||||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
if ([syncCache objectForKey: serverId])
|
||||||
|
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ActiveSyncEventFolder:
|
case ActiveSyncEventFolder:
|
||||||
|
@ -414,27 +476,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[o takeActiveSyncValues: allChanges inContext: context];
|
[o takeActiveSyncValues: allChanges inContext: context];
|
||||||
[sogoObject saveComponent: o];
|
[sogoObject saveComponent: o];
|
||||||
|
|
||||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
if ([syncCache objectForKey: serverId])
|
||||||
|
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ActiveSyncMailFolder:
|
case ActiveSyncMailFolder:
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
NSDictionary *result;
|
NSDictionary *result;
|
||||||
NSString *modseq;
|
NSNumber *modseq;
|
||||||
|
|
||||||
[sogoObject takeActiveSyncValues: allChanges inContext: context];
|
[sogoObject takeActiveSyncValues: allChanges inContext: context];
|
||||||
|
|
||||||
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
|
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
|
||||||
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
|
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
|
||||||
|
|
||||||
if (modseq)
|
if (modseq && [syncCache objectForKey: serverId])
|
||||||
[syncCache setObject: modseq forKey: serverId];
|
[syncCache setObject: [modseq stringValue] forKey: serverId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||||
|
|
||||||
|
|
||||||
[theBuffer appendString: @"<Change>"];
|
[theBuffer appendString: @"<Change>"];
|
||||||
|
@ -577,36 +639,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
withFilterType: (NSCalendarDate *) theFilterType
|
withFilterType: (NSCalendarDate *) theFilterType
|
||||||
inBuffer: (NSMutableString *) theBuffer
|
inBuffer: (NSMutableString *) theBuffer
|
||||||
lastServerKey: (NSString **) theLastServerKey
|
lastServerKey: (NSString **) theLastServerKey
|
||||||
|
defaultInterval: (unsigned int) theDefaultInterval
|
||||||
{
|
{
|
||||||
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
|
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
|
||||||
NSString *davCollectionTagToStore;
|
NSString *davCollectionTagToStore;
|
||||||
NSAutoreleasePool *pool;
|
NSAutoreleasePool *pool;
|
||||||
NSMutableString *s;
|
NSMutableString *s;
|
||||||
|
|
||||||
BOOL more_available;
|
BOOL cleanup_needed, more_available;
|
||||||
int i, max;
|
int i, max;
|
||||||
|
|
||||||
s = [NSMutableString string];
|
s = [NSMutableString string];
|
||||||
|
cleanup_needed = more_available = NO;
|
||||||
more_available = NO;
|
|
||||||
|
|
||||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||||
|
|
||||||
// If this is a new sync operation, DateCache and SyncCache needs to be deleted
|
// If this is a new sync operation, DateCache and SyncCache need to be deleted
|
||||||
if ([theSyncKey isEqualToString: @"-1"])
|
if ([theSyncKey isEqualToString: @"-1"])
|
||||||
{
|
{
|
||||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
|
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
|
||||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
|
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
|
||||||
}
|
}
|
||||||
|
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
|
||||||
|
{
|
||||||
|
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
|
||||||
|
// We need to cleanup this mess.
|
||||||
|
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@ syncKey: %@ cache: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login], theSyncKey, [folderMetadata objectForKey: @"SyncKey"]];
|
||||||
|
cleanup_needed = YES;
|
||||||
|
}
|
||||||
|
|
||||||
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
||||||
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
||||||
|
|
||||||
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
|
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
|
||||||
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
(cleanup_needed ||
|
||||||
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation
|
( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
||||||
theFilterType)
|
!([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
|
||||||
|
theFilterType
|
||||||
|
)
|
||||||
{
|
{
|
||||||
NSArray *allKeys;
|
NSArray *allKeys;
|
||||||
NSString *key;
|
NSString *key;
|
||||||
|
@ -622,14 +692,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
|
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
|
||||||
{
|
{
|
||||||
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
if ([syncCache objectForKey:key])
|
||||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
{
|
||||||
[s appendString: @"</SoftDelete>"];
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - SoftDelete %@", key];
|
||||||
|
|
||||||
[syncCache removeObjectForKey: key];
|
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
||||||
[dateCache removeObjectForKey: key];
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
||||||
|
[s appendString: @"</SoftDelete>"];
|
||||||
|
|
||||||
|
[syncCache removeObjectForKey: key];
|
||||||
|
//[dateCache removeObjectForKey: key];
|
||||||
|
|
||||||
softdelete_count++;
|
softdelete_count++;
|
||||||
|
}
|
||||||
|
else if (cleanup_needed)
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
|
||||||
|
|
||||||
|
// With this we make sure that a SoftDelete is set again on next sync.
|
||||||
|
[syncCache setObject: @"0" forKey: key];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
|
||||||
|
|
||||||
|
// Now we are save to remove the dateCache entry.
|
||||||
|
[dateCache removeObjectForKey: key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
||||||
|
@ -656,8 +748,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
//
|
//
|
||||||
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
|
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
more_available = NO;
|
|
||||||
|
|
||||||
davCollectionTagToStore = [theCollection davCollectionTag];
|
davCollectionTagToStore = [theCollection davCollectionTag];
|
||||||
|
|
||||||
|
@ -673,7 +763,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
NSDictionary *component;
|
NSDictionary *component;
|
||||||
NSArray *allComponents;
|
NSArray *allComponents;
|
||||||
|
|
||||||
BOOL updated;
|
BOOL updated, initialLoadInProgress;
|
||||||
int deleted, return_count;
|
int deleted, return_count;
|
||||||
|
|
||||||
if (theFolderType == ActiveSyncContactFolder)
|
if (theFolderType == ActiveSyncContactFolder)
|
||||||
|
@ -683,13 +773,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
else
|
else
|
||||||
component_name = @"vtodo";
|
component_name = @"vtodo";
|
||||||
|
|
||||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
initialLoadInProgress = NO;
|
||||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
|
|
||||||
|
if ([theSyncKey isEqualToString: @"-1"])
|
||||||
|
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||||
|
|
||||||
|
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||||
|
{
|
||||||
|
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||||
|
initialLoadInProgress = YES;
|
||||||
|
else
|
||||||
|
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||||
|
}
|
||||||
|
|
||||||
|
allComponents = [theCollection syncTokenFieldsWithProperties: nil
|
||||||
|
matchingSyncToken: theSyncKey
|
||||||
|
fromDate: theFilterType
|
||||||
|
initialLoad: initialLoadInProgress];
|
||||||
|
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
|
||||||
|
|
||||||
|
|
||||||
// Check for the WindowSize
|
// Check for the WindowSize
|
||||||
max = [allComponents count];
|
max = [allComponents count];
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cleanup the mess
|
||||||
|
//
|
||||||
|
if (cleanup_needed)
|
||||||
|
{
|
||||||
|
|
||||||
|
for (i = 0; i < max; i++)
|
||||||
|
{
|
||||||
|
component = [allComponents objectAtIndex: i];
|
||||||
|
deleted = [[component objectForKey: @"c_deleted"] intValue];
|
||||||
|
|
||||||
|
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
|
||||||
|
|
||||||
|
if (deleted)
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", uid];
|
||||||
|
|
||||||
|
// For deletes we have to recreate a cache entry to make sure the delete is sent again.
|
||||||
|
[syncCache setObject: @"0" forKey: uid];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ([syncCache objectForKey: uid] && [[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue])
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Cache cleanup: ADD %@", uid];
|
||||||
|
|
||||||
|
// Cleanup the cache to make sure the add is sent again.
|
||||||
|
[syncCache removeObjectForKey: uid];
|
||||||
|
[dateCache removeObjectForKey: uid];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", uid];
|
||||||
|
|
||||||
|
// Update cache entry to make sure the change is sent again.
|
||||||
|
[syncCache setObject: @"0" forKey: uid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return_count = 0;
|
return_count = 0;
|
||||||
|
|
||||||
for (i = 0; i < max; i++)
|
for (i = 0; i < max; i++)
|
||||||
|
@ -847,12 +1000,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
NSMutableArray *allCacheObjects, *sortedBySequence;
|
NSMutableArray *allCacheObjects, *sortedBySequence;
|
||||||
|
|
||||||
SOGoMailObject *mailObject;
|
SOGoMailObject *mailObject;
|
||||||
NSArray *allMessages;
|
NSArray *allMessages, *a;
|
||||||
|
NSString *firstUIDAdded;
|
||||||
|
|
||||||
int j, k, return_count;
|
int j, k, return_count, highestmodseq;
|
||||||
BOOL found_in_cache;
|
BOOL found_in_cache, initialLoadInProgress;
|
||||||
|
|
||||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
initialLoadInProgress = NO;
|
||||||
|
found_in_cache = NO;
|
||||||
|
firstUIDAdded = nil;
|
||||||
|
|
||||||
|
if ([theSyncKey isEqualToString: @"-1"])
|
||||||
|
{
|
||||||
|
highestmodseq = 0;
|
||||||
|
|
||||||
|
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
|
||||||
|
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a = [theSyncKey componentsSeparatedByString: @"-"];
|
||||||
|
highestmodseq = [[a objectAtIndex: 1] intValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||||
|
{
|
||||||
|
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||||
|
initialLoadInProgress = YES;
|
||||||
|
else
|
||||||
|
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||||
|
}
|
||||||
|
|
||||||
|
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||||
max = [allMessages count];
|
max = [allMessages count];
|
||||||
|
|
||||||
allCacheObjects = [NSMutableArray array];
|
allCacheObjects = [NSMutableArray array];
|
||||||
|
@ -860,7 +1039,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
for (i = 0; i < max; i++)
|
for (i = 0; i < max; i++)
|
||||||
{
|
{
|
||||||
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
|
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
|
||||||
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
||||||
}
|
}
|
||||||
|
|
||||||
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||||
|
@ -869,15 +1048,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
|
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
|
||||||
|
|
||||||
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]);
|
if (debugOn)
|
||||||
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]);
|
{
|
||||||
|
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
|
||||||
|
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
|
||||||
|
}
|
||||||
|
|
||||||
lastCacheObject = [sortedBySequence lastObject];
|
lastCacheObject = [sortedBySequence lastObject];
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cleanup the mess
|
||||||
|
//
|
||||||
|
if (cleanup_needed)
|
||||||
|
{
|
||||||
|
NSMutableArray *sortedByUID;
|
||||||
|
int uidnextFromCache;
|
||||||
|
|
||||||
|
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||||
|
[sortedByUID sortUsingSelector: @selector(compareUID:)];
|
||||||
|
|
||||||
|
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
|
||||||
|
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
|
||||||
|
uidnextFromCache = [[a objectAtIndex: 0] intValue];
|
||||||
|
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
|
||||||
|
|
||||||
|
// Remove all entries from cache beginning with the first uid added by the last sync request.
|
||||||
|
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
|
||||||
|
|
||||||
|
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||||
|
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||||
|
}
|
||||||
|
|
||||||
|
RELEASE(sortedByUID);
|
||||||
|
|
||||||
|
for (j = 0; j < [allCacheObjects count]; j++)
|
||||||
|
{
|
||||||
|
// Update the modseq in cache, sence othersie, it would be identical to the modseq from server
|
||||||
|
//and we would skip the cache when generating the response.
|
||||||
|
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||||
|
|
||||||
|
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||||
|
}
|
||||||
|
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||||
|
|
||||||
|
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
|
||||||
|
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject)
|
if (!cleanup_needed &&
|
||||||
|
[folderMetadata objectForKey: @"MoreAvailable"] &&
|
||||||
|
lastCacheObject &&
|
||||||
|
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
|
||||||
{
|
{
|
||||||
for (j = 0; j < [allCacheObjects count]; j++)
|
for (j = 0; j < [allCacheObjects count]; j++)
|
||||||
{
|
{
|
||||||
|
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
|
||||||
|
(![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
|
||||||
|
{
|
||||||
|
// We need to continue with adds or deletes from here.
|
||||||
|
found_in_cache = YES;
|
||||||
|
j--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
|
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
|
||||||
{
|
{
|
||||||
// Found out where we're at, let's continue from there...
|
// Found out where we're at, let's continue from there...
|
||||||
|
@ -892,12 +1138,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
if (found_in_cache)
|
if (found_in_cache)
|
||||||
k = j+1;
|
k = j+1;
|
||||||
else
|
else
|
||||||
{
|
j = k = 0;
|
||||||
k = 0;
|
|
||||||
j = 0;
|
if (debugOn)
|
||||||
}
|
[self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
|
||||||
|
|
||||||
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
|
|
||||||
|
|
||||||
return_count = 0;
|
return_count = 0;
|
||||||
|
|
||||||
|
@ -911,20 +1155,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
NSString *lastSequence;
|
NSString *lastSequence;
|
||||||
more_available = YES;
|
more_available = YES;
|
||||||
|
|
||||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
|
if (!firstUIDAdded)
|
||||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
{
|
||||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||||
|
firstUIDAdded = [a objectAtIndex: 0];
|
||||||
|
RETAIN(firstUIDAdded);
|
||||||
|
}
|
||||||
|
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
|
||||||
|
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, lastSequence];
|
||||||
|
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
|
||||||
|
|
||||||
DESTROY(pool);
|
DESTROY(pool);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
aCacheObject = [allCacheObjects objectAtIndex: k];
|
aCacheObject = [allCacheObjects objectAtIndex: k];
|
||||||
|
|
||||||
// If found in cache, it's either a Change or a Delete
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
|
||||||
|
|
||||||
|
// If found in cache, it's either a Change or a Delete operation.
|
||||||
if ([syncCache objectForKey: [aCacheObject uid]])
|
if ([syncCache objectForKey: [aCacheObject uid]])
|
||||||
{
|
{
|
||||||
if ([[aCacheObject sequence] isEqual: [NSNull null]])
|
if ([[aCacheObject sequence] isEqual: [NSNull null]])
|
||||||
{
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - DELETE!"];
|
||||||
|
|
||||||
// Deleted
|
// Deleted
|
||||||
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
||||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||||
|
@ -942,9 +1201,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
mailObject = [theCollection lookupName: [aCacheObject uid]
|
mailObject = [theCollection lookupName: [aCacheObject uid]
|
||||||
inContext: context
|
inContext: context
|
||||||
acquire: 0];
|
acquire: 0];
|
||||||
|
|
||||||
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
|
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
|
||||||
{
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - CHANGE!"];
|
||||||
|
|
||||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||||
|
@ -960,6 +1222,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - ADD!"];
|
||||||
|
|
||||||
// Added
|
// Added
|
||||||
if (![[aCacheObject sequence] isEqual: [NSNull null]])
|
if (![[aCacheObject sequence] isEqual: [NSNull null]])
|
||||||
{
|
{
|
||||||
|
@ -992,11 +1257,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
||||||
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
|
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
|
||||||
|
|
||||||
|
// Save the frist UID we add. We will use it for the synckey late.
|
||||||
|
if (!firstUIDAdded)
|
||||||
|
{
|
||||||
|
firstUIDAdded = [aCacheObject uid];
|
||||||
|
RETAIN(firstUIDAdded);
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - first uid added %@", firstUIDAdded];
|
||||||
|
}
|
||||||
|
|
||||||
return_count++;
|
return_count++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]);
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1005,16 +1281,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
if (more_available)
|
if (more_available)
|
||||||
{
|
{
|
||||||
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
|
||||||
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
|
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
||||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
|
||||||
|
if (firstUIDAdded)
|
||||||
|
{
|
||||||
|
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||||
|
[folderMetadata setObject: [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
|
||||||
|
RELEASE(firstUIDAdded);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
||||||
}
|
}
|
||||||
|
|
||||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||||
|
|
||||||
RELEASE(*theLastServerKey);
|
RELEASE(*theLastServerKey);
|
||||||
|
|
||||||
} // default:
|
} // default:
|
||||||
|
@ -1113,15 +1398,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
changeDetected: (BOOL *) changeDetected
|
changeDetected: (BOOL *) changeDetected
|
||||||
maxSyncResponseSize: (int) theMaxSyncResponseSize
|
maxSyncResponseSize: (int) theMaxSyncResponseSize
|
||||||
{
|
{
|
||||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache;
|
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache, *folderKey;
|
||||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
|
||||||
id collection, value;
|
|
||||||
|
|
||||||
NSMutableString *changeBuffer, *commandsBuffer;
|
|
||||||
BOOL getChanges, first_sync;
|
|
||||||
unsigned int windowSize, v, status;
|
|
||||||
NSMutableDictionary *folderMetadata, *folderOptions;
|
NSMutableDictionary *folderMetadata, *folderOptions;
|
||||||
|
NSMutableArray *supportedElements, *supportedElementNames;
|
||||||
|
NSMutableString *changeBuffer, *commandsBuffer;
|
||||||
|
id collection, value;
|
||||||
|
|
||||||
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||||
|
unsigned int windowSize, v, status, i;
|
||||||
|
BOOL getChanges, first_sync;
|
||||||
|
|
||||||
changeBuffer = [NSMutableString string];
|
changeBuffer = [NSMutableString string];
|
||||||
commandsBuffer = [NSMutableString string];
|
commandsBuffer = [NSMutableString string];
|
||||||
|
|
||||||
|
@ -1144,7 +1430,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
//[theBuffer appendString: @"</Collection>"];
|
//[theBuffer appendString: @"</Collection>"];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// First check if we have any concurrent Sync requests going on for this device.
|
||||||
|
// If we do and we are still within our maximumSyncInterval, we let our EAS
|
||||||
|
// device know to retry.
|
||||||
|
//
|
||||||
|
folderKey = [self _getNameInCache: collection withType: folderType];
|
||||||
|
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||||
|
|
||||||
// We check for a window size, default to 100 if not specfied or out of bounds
|
// We check for a window size, default to 100 if not specfied or out of bounds
|
||||||
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
|
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
|
||||||
|
|
||||||
|
@ -1169,13 +1463,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
first_sync = NO;
|
first_sync = NO;
|
||||||
|
|
||||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
|
||||||
|
|
||||||
if ([syncKey isEqualToString: @"0"])
|
if ([syncKey isEqualToString: @"0"])
|
||||||
{
|
{
|
||||||
davCollectionTag = @"-1";
|
davCollectionTag = @"-1";
|
||||||
first_sync = YES;
|
first_sync = YES;
|
||||||
*changeDetected = YES;
|
*changeDetected = YES;
|
||||||
|
|
||||||
|
supportedElementNames = [[[NSMutableArray alloc] init] autorelease];
|
||||||
|
value = [theDocumentElement getElementsByTagName: @"Supported"];
|
||||||
|
|
||||||
|
if ([value count])
|
||||||
|
{
|
||||||
|
supportedElements = (id)[[value lastObject] childNodes];
|
||||||
|
|
||||||
|
if ([supportedElements count])
|
||||||
|
{
|
||||||
|
for (i = 0; i < [supportedElements count]; i++)
|
||||||
|
{
|
||||||
|
if ([[supportedElements objectAtIndex: i] nodeType] == DOM_ELEMENT_NODE)
|
||||||
|
[supportedElementNames addObject: [[supportedElements objectAtIndex: i] tagName]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[folderMetadata setObject: supportedElementNames forKey: @"SupportedElements"];
|
||||||
|
|
||||||
|
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||||
|
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - %d %@: supportedElements saved: %@", [supportedElements count], [collection nameInContainer], supportedElementNames];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ((![syncKey isEqualToString: @"-1"]) && !([folderMetadata objectForKey: @"SyncCache"]))
|
else if ((![syncKey isEqualToString: @"-1"]) && !([folderMetadata objectForKey: @"SyncCache"]))
|
||||||
{
|
{
|
||||||
|
@ -1200,6 +1516,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// By default, send MIME mails. See #3146 for details.
|
// By default, send MIME mails. See #3146 for details.
|
||||||
if (!bodyPreferenceType)
|
if (!bodyPreferenceType)
|
||||||
bodyPreferenceType = @"4";
|
bodyPreferenceType = @"4";
|
||||||
|
|
||||||
|
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
||||||
|
|
||||||
|
if (!mimeSupport)
|
||||||
|
mimeSupport = @"1";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1225,11 +1546,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
{
|
{
|
||||||
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
|
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
|
||||||
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
||||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]];
|
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
||||||
|
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
||||||
|
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
|
||||||
|
|
||||||
//
|
//
|
||||||
// We process the commands from the request
|
// We process the commands from the request
|
||||||
|
@ -1268,10 +1591,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
withFolderType: folderType
|
withFolderType: folderType
|
||||||
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
|
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
|
||||||
inBuffer: changeBuffer
|
inBuffer: changeBuffer
|
||||||
lastServerKey: &lastServerKey];
|
lastServerKey: &lastServerKey
|
||||||
|
defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval]];
|
||||||
}
|
}
|
||||||
|
|
||||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||||
|
|
||||||
// If we got any changes or if we have applied any commands
|
// If we got any changes or if we have applied any commands
|
||||||
// let's regenerate our SyncKey based on the collection tag.
|
// let's regenerate our SyncKey based on the collection tag.
|
||||||
|
@ -1430,19 +1754,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
SOGoSystemDefaults *defaults;
|
SOGoSystemDefaults *defaults;
|
||||||
id <DOMElement> aCollection;
|
id <DOMElement> aCollection;
|
||||||
NSMutableString *output, *s;
|
NSMutableString *output, *s;
|
||||||
|
NSMutableDictionary *globalMetadata;
|
||||||
|
NSNumber *syncRequestInCache, *processIdentifier;
|
||||||
|
NSString *key;
|
||||||
NSArray *allCollections;
|
NSArray *allCollections;
|
||||||
NSData *d;
|
NSData *d;
|
||||||
|
|
||||||
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize;
|
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
|
||||||
BOOL changeDetected;
|
BOOL changeDetected;
|
||||||
|
|
||||||
changeDetected = NO;
|
|
||||||
|
|
||||||
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
|
||||||
|
|
||||||
// We initialize our output buffer
|
// We initialize our output buffer
|
||||||
output = [[NSMutableString alloc] init];
|
output = [[NSMutableString alloc] init];
|
||||||
|
|
||||||
|
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||||
|
defaultInterval = [defaults maximumSyncInterval];
|
||||||
|
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
||||||
|
|
||||||
|
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
||||||
|
|
||||||
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||||
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||||
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
|
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
|
||||||
|
@ -1457,12 +1786,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[output appendString: @"</Sync>"];
|
[output appendString: @"</Sync>"];
|
||||||
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||||
[theResponse setContent: d];
|
[theResponse setContent: d];
|
||||||
|
RELEASE(output);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let other requests know about the collections we are dealing with.
|
||||||
|
[self _setOrUnsetSyncRequest: YES collections: allCollections];
|
||||||
|
|
||||||
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
changeDetected = NO;
|
||||||
|
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
||||||
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
|
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
|
||||||
defaultInterval = [defaults maximumSyncInterval];
|
|
||||||
internalInterval = [defaults internalSyncInterval];
|
internalInterval = [defaults internalSyncInterval];
|
||||||
|
|
||||||
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
|
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
|
||||||
|
@ -1472,17 +1805,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// We check to see if our heartbeat interval falls into the supported ranges.
|
// We check to see if our heartbeat interval falls into the supported ranges.
|
||||||
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
|
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
|
||||||
{
|
{
|
||||||
|
int limit;
|
||||||
// Interval is too long, inform the client.
|
// Interval is too long, inform the client.
|
||||||
heartbeatInterval = defaultInterval;
|
heartbeatInterval = defaultInterval;
|
||||||
|
|
||||||
// Outlook doesn't like this...
|
// When Status = 14, the Wait interval is specified in minutes while
|
||||||
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval];
|
// defaultInterval is specifed in seconds. Adjust accordinlgy.
|
||||||
|
limit = defaultInterval/60;
|
||||||
|
if (limit < 1) limit = 1;
|
||||||
|
if (limit > 59) limit = 59;
|
||||||
|
//[output appendFormat: @"<Limit>%d</Limit>", limit];
|
||||||
//[output appendFormat: @"<Status>%d</Status>", 14];
|
//[output appendFormat: @"<Status>%d</Status>", 14];
|
||||||
}
|
}
|
||||||
|
|
||||||
[output appendString: @"<Collections>"];
|
[output appendString: @"<Collections>"];
|
||||||
|
|
||||||
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
|
||||||
|
|
||||||
// We enter our loop detection change
|
// We enter our loop detection change
|
||||||
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
|
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
|
||||||
|
@ -1492,25 +1828,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
for (j = 0; j < [allCollections count]; j++)
|
for (j = 0; j < [allCollections count]; j++)
|
||||||
{
|
{
|
||||||
aCollection = [allCollections objectAtIndex: j];
|
aCollection = [allCollections objectAtIndex: j];
|
||||||
|
|
||||||
[self processSyncCollection: aCollection
|
[self processSyncCollection: aCollection
|
||||||
inBuffer: s
|
inBuffer: s
|
||||||
changeDetected: &changeDetected
|
changeDetected: &changeDetected
|
||||||
maxSyncResponseSize: maxSyncResponseSize];
|
maxSyncResponseSize: maxSyncResponseSize];
|
||||||
|
|
||||||
if (maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize)
|
// Don't return a response if another Sync is waiting.
|
||||||
|
globalMetadata = [self globalMetadataForDevice];
|
||||||
|
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
||||||
|
|
||||||
|
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
|
||||||
|
|
||||||
|
[theResponse setStatus: 503];
|
||||||
|
|
||||||
|
RELEASE(output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changeDetected)
|
if (changeDetected)
|
||||||
{
|
{
|
||||||
[self logWithFormat: @"Change detected, we push the content."];
|
[self logWithFormat: @"Change detected during Sync, we push the content."];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (heartbeatInterval > 1)
|
else if (heartbeatInterval > 1)
|
||||||
{
|
{
|
||||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
total_sleep = 0;
|
||||||
sleep(internalInterval);
|
|
||||||
|
while (total_sleep < internalInterval)
|
||||||
|
{
|
||||||
|
// We check if we must break the current synchronization since an other Sync
|
||||||
|
// has just arrived.
|
||||||
|
syncRequestInCache = [[self globalMetadataForDevice] objectForKey: @"SyncRequest"];
|
||||||
|
if (!([syncRequest isEqualToNumber: syncRequestInCache]))
|
||||||
|
{
|
||||||
|
if (debugOn)
|
||||||
|
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
|
||||||
|
|
||||||
|
// Make sure we end the heardbeat-loop.
|
||||||
|
heartbeatInterval = internalInterval = 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Sync...", internalInterval-total_sleep];
|
||||||
|
sleep(5);
|
||||||
|
total_sleep += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1518,8 +1891,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response.
|
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
|
||||||
|
// otherwise send an empty response.
|
||||||
|
//
|
||||||
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
|
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
|
||||||
{
|
{
|
||||||
// We always return the last generated response.
|
// We always return the last generated response.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Copyright (c) 2014, Inverse inc.
|
Copyright (c) 2014-2015, Inverse inc.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -31,15 +31,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include "SOGoActiveSyncConstants.h"
|
#include "SOGoActiveSyncConstants.h"
|
||||||
|
|
||||||
|
@class NSCalendarDate;
|
||||||
@class NSException;
|
@class NSException;
|
||||||
|
@class NSMutableDictionary;
|
||||||
@class NSURL;
|
@class NSURL;
|
||||||
|
@class NSNumber;
|
||||||
|
|
||||||
@interface SOGoActiveSyncDispatcher : NSObject
|
@interface SOGoActiveSyncDispatcher : NSObject
|
||||||
{
|
{
|
||||||
NSURL *folderTableURL;
|
NSURL *folderTableURL;
|
||||||
|
NSDictionary *imapFolderGUIDS;
|
||||||
id context;
|
id context;
|
||||||
|
|
||||||
|
NSNumber *syncRequest;
|
||||||
|
|
||||||
|
BOOL debugOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSMutableDictionary *) globalMetadataForDevice;
|
||||||
|
|
||||||
- (id) collectionFromId: (NSString *) theCollectionId
|
- (id) collectionFromId: (NSString *) theCollectionId
|
||||||
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
|
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "SOGoActiveSyncDispatcher.h"
|
#include "SOGoActiveSyncDispatcher.h"
|
||||||
|
|
||||||
#import <Foundation/NSArray.h>
|
#import <Foundation/NSArray.h>
|
||||||
|
#import <Foundation/NSData.h>
|
||||||
#import <Foundation/NSAutoreleasePool.h>
|
#import <Foundation/NSAutoreleasePool.h>
|
||||||
#import <Foundation/NSCalendarDate.h>
|
#import <Foundation/NSCalendarDate.h>
|
||||||
|
#if GNUSTEP_BASE_MINOR_VERSION >= 21
|
||||||
|
#import <Foundation/NSLocale.h>
|
||||||
|
#endif
|
||||||
#import <Foundation/NSProcessInfo.h>
|
#import <Foundation/NSProcessInfo.h>
|
||||||
#import <Foundation/NSTimeZone.h>
|
#import <Foundation/NSTimeZone.h>
|
||||||
#import <Foundation/NSURL.h>
|
#import <Foundation/NSURL.h>
|
||||||
|
@ -134,6 +138,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENSSL
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
@interface SOGoActiveSyncDispatcher (Sync)
|
@interface SOGoActiveSyncDispatcher (Sync)
|
||||||
|
|
||||||
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
|
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
|
||||||
|
@ -143,20 +153,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
@implementation SOGoActiveSyncDispatcher
|
@implementation SOGoActiveSyncDispatcher
|
||||||
|
|
||||||
static BOOL debugOn = NO;
|
|
||||||
|
|
||||||
- (id) init
|
- (id) init
|
||||||
{
|
{
|
||||||
[super init];
|
[super init];
|
||||||
|
|
||||||
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
||||||
folderTableURL = nil;
|
folderTableURL = nil;
|
||||||
|
imapFolderGUIDS = nil;
|
||||||
|
syncRequest = nil;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) dealloc
|
- (void) dealloc
|
||||||
{
|
{
|
||||||
RELEASE(folderTableURL);
|
RELEASE(folderTableURL);
|
||||||
|
RELEASE(imapFolderGUIDS);
|
||||||
|
RELEASE(syncRequest);
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,16 +181,16 @@ static BOOL debugOn = NO;
|
||||||
[o setTableUrl: [self folderTableURL]];
|
[o setTableUrl: [self folderTableURL]];
|
||||||
[o reloadIfNeeded];
|
[o reloadIfNeeded];
|
||||||
|
|
||||||
[[o properties] removeAllObjects];
|
[[o properties] setObject: theSyncKey
|
||||||
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
|
forKey: @"FolderSyncKey"];
|
||||||
[o save];
|
[o save];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSMutableDictionary *) _globalMetadataForDevice
|
- (NSMutableDictionary *) globalMetadataForDevice
|
||||||
{
|
{
|
||||||
SOGoCacheGCSObject *o;
|
SOGoCacheGCSObject *o;
|
||||||
|
|
||||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
|
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||||
[o setTableUrl: [self folderTableURL]];
|
[o setTableUrl: [self folderTableURL]];
|
||||||
[o reloadIfNeeded];
|
[o reloadIfNeeded];
|
||||||
|
@ -202,7 +214,7 @@ static BOOL debugOn = NO;
|
||||||
if (theFilter)
|
if (theFilter)
|
||||||
{
|
{
|
||||||
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
|
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
|
||||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||||
[o setTableUrl: [self folderTableURL]];
|
[o setTableUrl: [self folderTableURL]];
|
||||||
[o reloadIfNeeded];
|
[o reloadIfNeeded];
|
||||||
|
|
||||||
|
@ -229,23 +241,27 @@ static BOOL debugOn = NO;
|
||||||
SOGoMailAccounts *accountsFolder;
|
SOGoMailAccounts *accountsFolder;
|
||||||
SOGoMailAccount *accountFolder;
|
SOGoMailAccount *accountFolder;
|
||||||
SOGoUserFolder *userFolder;
|
SOGoUserFolder *userFolder;
|
||||||
NSDictionary *imapGUIDs;
|
|
||||||
|
|
||||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
if (!imapFolderGUIDS)
|
||||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
{
|
||||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||||
|
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||||
// Get the GUID of the IMAP folder
|
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
|
||||||
|
// Get the GUID of the IMAP folder
|
||||||
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
|
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
|
||||||
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
[imapFolderGUIDS retain];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||||
}
|
}
|
||||||
|
|
||||||
return theIdToTranslate;
|
return theIdToTranslate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
@ -691,26 +707,25 @@ static BOOL debugOn = NO;
|
||||||
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
|
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
|
||||||
inResponse: (WOResponse *) theResponse
|
inResponse: (WOResponse *) theResponse
|
||||||
{
|
{
|
||||||
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType;
|
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
|
||||||
|
NSMutableDictionary *cachedGUIDs, *metadata;
|
||||||
|
NSMutableArray *folders, *processedFolders;
|
||||||
NSDictionary *folderMetadata, *imapGUIDs;
|
NSDictionary *folderMetadata, *imapGUIDs;
|
||||||
NSArray *allFoldersMetadata, *allKeys;
|
NSArray *allFoldersMetadata, *allKeys;
|
||||||
NSMutableDictionary *cachedGUIDs, *metadata;
|
|
||||||
SOGoMailAccounts *accountsFolder;
|
SOGoMailAccounts *accountsFolder;
|
||||||
SOGoMailAccount *accountFolder;
|
SOGoMailAccount *accountFolder;
|
||||||
NSMutableString *s, *commands;
|
NSMutableString *s, *commands;
|
||||||
SOGoUserFolder *userFolder;
|
SOGoUserFolder *userFolder;
|
||||||
NSMutableArray *folders, *processedFolders;
|
|
||||||
SoSecurityManager *sm;
|
SoSecurityManager *sm;
|
||||||
SOGoCacheGCSObject *o;
|
SOGoCacheGCSObject *o;
|
||||||
id currentFolder;
|
id currentFolder;
|
||||||
NSData *d;
|
NSData *d;
|
||||||
|
|
||||||
int status, command_count, i, type, fi, count;
|
int status, command_count, i, type, fi, count;
|
||||||
|
|
||||||
BOOL first_sync;
|
BOOL first_sync;
|
||||||
|
|
||||||
sm = [SoSecurityManager sharedSecurityManager];
|
sm = [SoSecurityManager sharedSecurityManager];
|
||||||
metadata = [self _globalMetadataForDevice];
|
metadata = [self globalMetadataForDevice];
|
||||||
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||||
s = [NSMutableString string];
|
s = [NSMutableString string];
|
||||||
|
|
||||||
|
@ -824,32 +839,38 @@ static BOOL debugOn = NO;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
|
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
|
||||||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
|
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
|
||||||
folderType = @"Calendar";
|
folderType = @"Calendar";
|
||||||
else
|
else
|
||||||
folderType = @"Contacts";
|
folderType = @"Contacts";
|
||||||
|
|
||||||
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
|
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
|
||||||
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
|
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
|
||||||
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
|
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
|
||||||
|
|
||||||
// remove the folder from device if it doesn't exists or it has not the proper permissions
|
// We skip personal GCS folders - we always want to synchronize these
|
||||||
if (!currentFolder ||
|
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
||||||
[sm validatePermission: SoPerm_DeleteObjects
|
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
||||||
onObject: currentFolder
|
continue;
|
||||||
inContext: context] ||
|
|
||||||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
// Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions
|
||||||
onObject: currentFolder
|
if (!currentFolder ||
|
||||||
inContext: context])
|
![currentFolder synchronize] ||
|
||||||
{
|
[sm validatePermission: SoPerm_DeleteObjects
|
||||||
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
|
onObject: currentFolder
|
||||||
command_count++;
|
inContext: context] ||
|
||||||
[o destroy];
|
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
||||||
}
|
onObject: currentFolder
|
||||||
}
|
inContext: context])
|
||||||
}
|
{
|
||||||
}
|
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
|
||||||
|
command_count++;
|
||||||
|
[o destroy];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle addition and changes
|
// Handle addition and changes
|
||||||
for (i = 0; i < [allFoldersMetadata count]; i++)
|
for (i = 0; i < [allFoldersMetadata count]; i++)
|
||||||
|
@ -937,7 +958,9 @@ static BOOL debugOn = NO;
|
||||||
[[o properties] removeObjectForKey: @"DateCache"];
|
[[o properties] removeObjectForKey: @"DateCache"];
|
||||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||||
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||||
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||||
[o save];
|
[o save];
|
||||||
|
|
||||||
command_count++;
|
command_count++;
|
||||||
|
@ -946,17 +969,29 @@ static BOOL debugOn = NO;
|
||||||
|
|
||||||
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
|
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
|
||||||
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
|
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
|
||||||
|
[folders autorelease];
|
||||||
|
|
||||||
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
|
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
|
||||||
|
|
||||||
// Inside this loop we remove all the folder without write/delete permissions
|
// We remove all the folders that aren't GCS-ones, that we don't want to synchronize and
|
||||||
|
// the ones without write/delete permissions.
|
||||||
count = [folders count]-1;
|
count = [folders count]-1;
|
||||||
for (; count >= 0; count--)
|
for (; count >= 0; count--)
|
||||||
{
|
{
|
||||||
if ([sm validatePermission: SoPerm_DeleteObjects
|
currentFolder = [folders objectAtIndex: count];
|
||||||
onObject: [folders objectAtIndex: count]
|
|
||||||
|
// We skip personal GCS folders - we always want to synchronize these
|
||||||
|
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
|
||||||
|
[[currentFolder nameInContainer] isEqualToString: @"personal"])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] ||
|
||||||
|
![currentFolder synchronize] ||
|
||||||
|
[sm validatePermission: SoPerm_DeleteObjects
|
||||||
|
onObject: currentFolder
|
||||||
inContext: context] ||
|
inContext: context] ||
|
||||||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
|
||||||
onObject: [folders objectAtIndex: count]
|
onObject: currentFolder
|
||||||
inContext: context])
|
inContext: context])
|
||||||
{
|
{
|
||||||
[folders removeObjectAtIndex: count];
|
[folders removeObjectAtIndex: count];
|
||||||
|
@ -964,7 +999,6 @@ static BOOL debugOn = NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
count = [folders count]-1;
|
count = [folders count]-1;
|
||||||
NSString *operation;
|
|
||||||
|
|
||||||
for (fi = 0; fi <= count ; fi++)
|
for (fi = 0; fi <= count ; fi++)
|
||||||
{
|
{
|
||||||
|
@ -1022,7 +1056,9 @@ static BOOL debugOn = NO;
|
||||||
[[o properties] removeObjectForKey: @"DateCache"];
|
[[o properties] removeObjectForKey: @"DateCache"];
|
||||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||||
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||||
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||||
}
|
}
|
||||||
|
|
||||||
[o save];
|
[o save];
|
||||||
|
@ -1045,7 +1081,9 @@ static BOOL debugOn = NO;
|
||||||
[[o properties] removeObjectForKey: @"DateCache"];
|
[[o properties] removeObjectForKey: @"DateCache"];
|
||||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||||
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
||||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||||
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||||
}
|
}
|
||||||
|
|
||||||
[o save];
|
[o save];
|
||||||
|
@ -1204,7 +1242,7 @@ static BOOL debugOn = NO;
|
||||||
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
||||||
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||||
|
|
||||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
|
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
|
||||||
|
|
||||||
count = [allMessages count];
|
count = [allMessages count];
|
||||||
|
|
||||||
|
@ -1254,7 +1292,7 @@ static BOOL debugOn = NO;
|
||||||
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
|
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
|
||||||
inResponse: (WOResponse *) theResponse
|
inResponse: (WOResponse *) theResponse
|
||||||
{
|
{
|
||||||
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId;
|
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
|
||||||
NSMutableString *s;
|
NSMutableString *s;
|
||||||
NSArray *fetchRequests;
|
NSArray *fetchRequests;
|
||||||
id aFetch;
|
id aFetch;
|
||||||
|
@ -1367,6 +1405,8 @@ static BOOL debugOn = NO;
|
||||||
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||||
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
||||||
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
||||||
|
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
||||||
|
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
||||||
|
|
||||||
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
||||||
|
|
||||||
|
@ -2024,13 +2064,13 @@ static BOOL debugOn = NO;
|
||||||
|
|
||||||
if ([foldersWithChanges count])
|
if ([foldersWithChanges count])
|
||||||
{
|
{
|
||||||
[self logWithFormat: @"Change detected, we push the content."];
|
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
|
||||||
status = 2;
|
status = 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
|
||||||
sleep(internalInterval);
|
sleep(internalInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2094,6 +2134,123 @@ static BOOL debugOn = NO;
|
||||||
[theResponse setContent: d];
|
[theResponse setContent: d];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
#ifdef HAVE_OPENSSL
|
||||||
|
- (unsigned int) validateCert: (NSString *) theCert
|
||||||
|
{
|
||||||
|
NSData *d;
|
||||||
|
|
||||||
|
const unsigned char *data;
|
||||||
|
X509_STORE_CTX *ctx;
|
||||||
|
X509_LOOKUP *lookup;
|
||||||
|
X509_STORE *store;
|
||||||
|
X509 *cert;
|
||||||
|
|
||||||
|
BOOL success;
|
||||||
|
size_t len;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
success = NO;
|
||||||
|
|
||||||
|
d = [theCert dataByDecodingBase64];
|
||||||
|
data = (unsigned char *)[d bytes];
|
||||||
|
len = [d length];
|
||||||
|
|
||||||
|
cert = d2i_X509(NULL, &data, len);
|
||||||
|
if (!cert)
|
||||||
|
{
|
||||||
|
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
|
||||||
|
return 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
store = X509_STORE_new();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
|
||||||
|
if (store)
|
||||||
|
{
|
||||||
|
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
||||||
|
if (lookup)
|
||||||
|
{
|
||||||
|
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
|
||||||
|
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
|
||||||
|
if (lookup)
|
||||||
|
{
|
||||||
|
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
|
||||||
|
ERR_clear_error();
|
||||||
|
success = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
if (store)
|
||||||
|
{
|
||||||
|
X509_STORE_free(store);
|
||||||
|
store = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = X509_STORE_CTX_new();
|
||||||
|
if (!ctx)
|
||||||
|
{
|
||||||
|
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
|
||||||
|
return 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
|
||||||
|
{
|
||||||
|
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
|
||||||
|
X509_STORE_CTX_free(ctx);
|
||||||
|
return 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = X509_verify_cert(ctx);
|
||||||
|
X509_STORE_CTX_free(ctx);
|
||||||
|
X509_free(cert);
|
||||||
|
|
||||||
|
if (rc)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
|
||||||
|
return 17;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
- (unsigned int) validateCert: (NSString *) theCert
|
||||||
|
{
|
||||||
|
return 17;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
|
||||||
|
inResponse: (WOResponse *) theResponse
|
||||||
|
{
|
||||||
|
NSMutableString *s;
|
||||||
|
NSString *cert;
|
||||||
|
NSData *d;
|
||||||
|
|
||||||
|
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
|
||||||
|
|
||||||
|
s = [NSMutableString string];
|
||||||
|
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||||
|
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||||
|
[s appendString: @"<ValidateCert xmlns=\"ValidateCert:\">"];
|
||||||
|
[s appendString: @"<Status>1</Status><Certificate>"];
|
||||||
|
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
|
||||||
|
[s appendString: @"</Certificate></ValidateCert>"];
|
||||||
|
|
||||||
|
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||||
|
|
||||||
|
[theResponse setContent: d];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// <?xml version="1.0"?>
|
// <?xml version="1.0"?>
|
||||||
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
||||||
|
@ -2410,39 +2567,100 @@ static BOOL debugOn = NO;
|
||||||
NGMimeMessageParser *parser;
|
NGMimeMessageParser *parser;
|
||||||
NGMimeMessage *message;
|
NGMimeMessage *message;
|
||||||
NSException *error;
|
NSException *error;
|
||||||
NSData *data;
|
NSMutableData *data;
|
||||||
NGMutableHashMap *map;
|
NSData *new_from_header;
|
||||||
NGMimeMessage *messageToSend;
|
|
||||||
NGMimeMessageGenerator *generator;
|
|
||||||
NSDictionary *identity;
|
NSDictionary *identity;
|
||||||
NSString *fullName, *email;
|
NSString *fullName, *email;
|
||||||
|
|
||||||
|
const char *bytes;
|
||||||
|
int i, e, len;
|
||||||
|
BOOL found_header;
|
||||||
|
|
||||||
// We get the mail's data
|
// We get the mail's data
|
||||||
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
|
data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
|
||||||
|
|
||||||
// We extract the recipients
|
// We extract the recipients
|
||||||
parser = [[NGMimeMessageParser alloc] init];
|
parser = [[NGMimeMessageParser alloc] init];
|
||||||
message = [parser parsePartFromData: data];
|
message = [parser parsePartFromData: data];
|
||||||
RELEASE(parser);
|
RELEASE(parser);
|
||||||
|
|
||||||
map = [NGHashMap hashMapWithDictionary: [message headers]];
|
|
||||||
|
|
||||||
identity = [[context activeUser] primaryIdentity];
|
identity = [[context activeUser] primaryIdentity];
|
||||||
|
|
||||||
fullName = [identity objectForKey: @"fullName"];
|
fullName = [identity objectForKey: @"fullName"];
|
||||||
email = [identity objectForKey: @"email"];
|
email = [identity objectForKey: @"email"];
|
||||||
|
|
||||||
if ([fullName length])
|
if ([fullName length])
|
||||||
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"];
|
new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", [fullName asQPSubjectString: @"utf-8"], email] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
else
|
else
|
||||||
[map setObject: email forKey: @"from"];
|
new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
|
||||||
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
|
bytes = [data bytes];
|
||||||
|
len = [data length];
|
||||||
|
i = 0;
|
||||||
|
found_header = NO;
|
||||||
|
|
||||||
[messageToSend setBody: [message body]];
|
// Search for the from-header
|
||||||
|
while (i < len)
|
||||||
|
{
|
||||||
|
if (i == 0 &&
|
||||||
|
(*bytes == 'f' || *bytes == 'F') &&
|
||||||
|
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
|
||||||
|
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
|
||||||
|
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
|
||||||
|
(*(bytes+4) == ':'))
|
||||||
|
{
|
||||||
|
found_header = YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
|
||||||
|
(*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
|
||||||
|
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
|
||||||
|
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
|
||||||
|
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
|
||||||
|
(*(bytes+6) == ':'))
|
||||||
|
{
|
||||||
|
found_header = YES;
|
||||||
|
i = i + 2; // \r\n
|
||||||
|
bytes = bytes + 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We search for the first \r\n AFTER the From: header to get the length of the string to replace.
|
||||||
|
e = i;
|
||||||
|
while (e < len)
|
||||||
|
{
|
||||||
|
if ((*bytes == '\r') && (*(bytes+1) == '\n'))
|
||||||
|
{
|
||||||
|
e = e + 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes++;
|
||||||
|
e++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update/Add the From header in the MIMEBody of the SendMail request.
|
||||||
|
// Any other way to modify the mail body would break s/mime emails.
|
||||||
|
if (found_header)
|
||||||
|
{
|
||||||
|
// Change the From header
|
||||||
|
[data replaceBytesInRange: NSMakeRange(i, (NSUInteger)(e-i))
|
||||||
|
withBytes: [new_from_header bytes]
|
||||||
|
length: [new_from_header length]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Add a From header
|
||||||
|
[data replaceBytesInRange: NSMakeRange(0, 0)
|
||||||
|
withBytes: [new_from_header bytes]
|
||||||
|
length: [new_from_header length]];
|
||||||
|
}
|
||||||
|
|
||||||
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
|
|
||||||
data = [generator generateMimeFromPart: messageToSend];
|
|
||||||
|
|
||||||
error = [self _sendMail: data
|
error = [self _sendMail: data
|
||||||
recipients: [message allRecipients]
|
recipients: [message allRecipients]
|
||||||
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
|
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
|
||||||
|
@ -2619,6 +2837,11 @@ static BOOL debugOn = NO;
|
||||||
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
||||||
|
|
||||||
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
|
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
|
||||||
|
|
||||||
|
// If there is no References: header, initialize it with In-Reply-To.
|
||||||
|
if ([mailObject inReplyTo] && ![references count])
|
||||||
|
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
|
||||||
|
|
||||||
if ([references count] > 0)
|
if ([references count] > 0)
|
||||||
{
|
{
|
||||||
// If there are more than ten identifiers listed, we eliminate the second one.
|
// If there are more than ten identifiers listed, we eliminate the second one.
|
||||||
|
@ -2934,9 +3157,25 @@ static BOOL debugOn = NO;
|
||||||
options: NSCaseInsensitiveSearch].location == NSNotFound)
|
options: NSCaseInsensitiveSearch].location == NSNotFound)
|
||||||
{
|
{
|
||||||
NSString *value;
|
NSString *value;
|
||||||
|
#if GNUSTEP_BASE_MINOR_VERSION < 21
|
||||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
|
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
||||||
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]];
|
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
||||||
|
locale: nil];
|
||||||
|
#else
|
||||||
|
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
||||||
|
locale: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
[NSArray arrayWithObjects: @"Jan", @"Feb", @"Mar", @"Apr",
|
||||||
|
@"May", @"Jun", @"Jul", @"Aug",
|
||||||
|
@"Sep", @"Oct", @"Nov", @"Dec", nil],
|
||||||
|
@"NSShortMonthNameArray",
|
||||||
|
[NSArray arrayWithObjects: @"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
|
||||||
|
@"Fri", @"Sat", nil],
|
||||||
|
@"NSShortWeekDayNameArray",
|
||||||
|
nil]];
|
||||||
|
|
||||||
|
#endif
|
||||||
|
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -3026,8 +3265,7 @@ static BOOL debugOn = NO;
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
urlString = [[user domainDefaults] folderInfoURL];
|
urlString = [[user domainDefaults] folderInfoURL];
|
||||||
parts = [[urlString componentsSeparatedByString: @"/"]
|
parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
|
||||||
mutableCopy];
|
|
||||||
[parts autorelease];
|
[parts autorelease];
|
||||||
if ([parts count] == 5)
|
if ([parts count] == 5)
|
||||||
{
|
{
|
||||||
|
|
|
@ -472,18 +472,46 @@ struct GlobalObjectId {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
- (BOOL) _sanitinizeNeeded: (NSArray *) theParts
|
||||||
|
{
|
||||||
|
NSString *encoding, *charset;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < [theParts count]; i++)
|
||||||
|
{
|
||||||
|
encoding = [[theParts objectAtIndex: i ] objectForKey: @"encoding"];
|
||||||
|
charset = [[[theParts objectAtIndex: i ] objectForKey: @"parameterList"] objectForKey: @"charset"];
|
||||||
|
if (encoding && [encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame &&
|
||||||
|
charset && ![charset caseInsensitiveCompare: @"utf-8"] == NSOrderedSame
|
||||||
|
&& ![charset caseInsensitiveCompare: @"us-ascii"] == NSOrderedSame)
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self _sanitinizeNeeded: [[theParts objectAtIndex: i ] objectForKey: @"parts"]])
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
- (NSData *) _preferredBodyDataUsingType: (int) theType
|
- (NSData *) _preferredBodyDataUsingType: (int) theType
|
||||||
|
mimeSupport: (int) theMimeSupport
|
||||||
nativeType: (int *) theNativeType
|
nativeType: (int *) theNativeType
|
||||||
{
|
{
|
||||||
NSString *type, *subtype, *encoding;
|
NSString *type, *subtype, *encoding;
|
||||||
NSData *d;
|
NSData *d;
|
||||||
|
BOOL isSMIME, sanitinizeNeeded;
|
||||||
|
|
||||||
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
|
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
|
||||||
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||||
|
isSMIME = NO;
|
||||||
d = nil;
|
d = nil;
|
||||||
|
|
||||||
// We determine the native type
|
// We determine the native type
|
||||||
|
@ -494,8 +522,14 @@ struct GlobalObjectId {
|
||||||
else if ([type isEqualToString: @"multipart"])
|
else if ([type isEqualToString: @"multipart"])
|
||||||
*theNativeType = 4;
|
*theNativeType = 4;
|
||||||
|
|
||||||
|
if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && theMimeSupport > 0)
|
||||||
|
{
|
||||||
|
*theNativeType = 4;
|
||||||
|
isSMIME = YES;
|
||||||
|
}
|
||||||
|
|
||||||
// We get the right part based on the preference
|
// We get the right part based on the preference
|
||||||
if (theType == 1 || theType == 2)
|
if ((theType == 1 || theType == 2) && !isSMIME)
|
||||||
{
|
{
|
||||||
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
|
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
|
||||||
{
|
{
|
||||||
|
@ -536,12 +570,12 @@ struct GlobalObjectId {
|
||||||
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
|
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (theType == 4)
|
else if (theType == 4 || isSMIME)
|
||||||
{
|
{
|
||||||
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
|
// We sanitize the content if the content-transfer-encoding is 8bit and charset is not utf-8 or us-ascii.
|
||||||
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
|
sanitinizeNeeded = [self _sanitinizeNeeded: [NSArray arrayWithObject: [self bodyStructure]]];
|
||||||
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
|
|
||||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
|
if (sanitinizeNeeded && !isSMIME)
|
||||||
d = [self _sanitizedMIMEMessage];
|
d = [self _sanitizedMIMEMessage];
|
||||||
else
|
else
|
||||||
d = [self content];
|
d = [self content];
|
||||||
|
@ -656,16 +690,18 @@ struct GlobalObjectId {
|
||||||
{
|
{
|
||||||
NSData *d, *globalObjId;
|
NSData *d, *globalObjId;
|
||||||
NSArray *attachmentKeys;
|
NSArray *attachmentKeys;
|
||||||
NSMutableString *s;
|
|
||||||
|
|
||||||
uint32_t v;
|
|
||||||
NSString *p;
|
|
||||||
|
|
||||||
id value;
|
|
||||||
|
|
||||||
iCalCalendar *calendar;
|
iCalCalendar *calendar;
|
||||||
|
NSString *p, *subtype;
|
||||||
|
NSMutableString *s;
|
||||||
|
id value;
|
||||||
|
|
||||||
int preferredBodyType, nativeBodyType;
|
int preferredBodyType, mimeSupport, nativeBodyType;
|
||||||
|
uint32_t v;
|
||||||
|
|
||||||
|
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||||
|
|
||||||
|
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
||||||
|
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
|
||||||
|
|
||||||
s = [NSMutableString string];
|
s = [NSMutableString string];
|
||||||
|
|
||||||
|
@ -862,7 +898,12 @@ struct GlobalObjectId {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// MesssageClass and ContentClass
|
// MesssageClass and ContentClass
|
||||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
|
if ([subtype isEqualToString: @"signed"])
|
||||||
|
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME.MultipartSigned"];
|
||||||
|
else if ([subtype isEqualToString: @"pkcs7-mime"])
|
||||||
|
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME"];
|
||||||
|
else
|
||||||
|
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
|
||||||
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
|
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -876,10 +917,8 @@ struct GlobalObjectId {
|
||||||
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
|
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
|
||||||
|
|
||||||
// Body - namespace 17
|
// Body - namespace 17
|
||||||
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
|
||||||
|
|
||||||
nativeBodyType = 1;
|
nativeBodyType = 1;
|
||||||
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
|
d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
|
||||||
|
|
||||||
if (calendar && !d)
|
if (calendar && !d)
|
||||||
{
|
{
|
||||||
|
@ -981,9 +1020,12 @@ struct GlobalObjectId {
|
||||||
{
|
{
|
||||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||||
|
|
||||||
// Set the correct type if client requested text/html but we got text/plain
|
// Set the correct type if client requested text/html but we got text/plain.
|
||||||
|
// For s/mime mails type is always 4 if mimeSupport is 1 or 2.
|
||||||
if (preferredBodyType == 2 && nativeBodyType == 1)
|
if (preferredBodyType == 2 && nativeBodyType == 1)
|
||||||
[s appendString: @"<Type>1</Type>"];
|
[s appendString: @"<Type>1</Type>"];
|
||||||
|
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
|
||||||
|
[s appendString: @"<Type>4</Type>"];
|
||||||
else
|
else
|
||||||
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
|
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
|
||||||
|
|
||||||
|
@ -1001,7 +1043,8 @@ struct GlobalObjectId {
|
||||||
|
|
||||||
// Attachments -namespace 16
|
// Attachments -namespace 16
|
||||||
attachmentKeys = [self fetchFileAttachmentKeys];
|
attachmentKeys = [self fetchFileAttachmentKeys];
|
||||||
if ([attachmentKeys count])
|
|
||||||
|
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -1071,9 +1114,15 @@ struct GlobalObjectId {
|
||||||
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
|
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
|
||||||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
|
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
|
||||||
{
|
{
|
||||||
|
id value;
|
||||||
NSString *reference;
|
NSString *reference;
|
||||||
|
|
||||||
reference = [[[[self mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
value = [[self mailHeaders] objectForKey: @"references"];
|
||||||
|
|
||||||
|
if ([value isKindOfClass: [NSArray class]])
|
||||||
|
reference = [[[value objectAtIndex: 0] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||||
|
else
|
||||||
|
reference = [[value componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||||
|
|
||||||
if ([reference length] > 0)
|
if ([reference length] > 0)
|
||||||
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
||||||
|
|
|
@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
@interface SOGoSyncCacheObject : NSObject
|
@interface SOGoSyncCacheObject : NSObject
|
||||||
{
|
{
|
||||||
|
@public
|
||||||
id _uid;
|
id _uid;
|
||||||
id _sequence;
|
id _sequence;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#import <Foundation/NSNull.h>
|
#import <Foundation/NSNull.h>
|
||||||
#import <Foundation/NSString.h>
|
#import <Foundation/NSString.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
|
|
||||||
|
static Class NSNullK;
|
||||||
|
|
||||||
@implementation SOGoSyncCacheObject
|
@implementation SOGoSyncCacheObject
|
||||||
|
|
||||||
|
+ (void) initialize
|
||||||
|
{
|
||||||
|
NSNullK = [NSNull class];
|
||||||
|
}
|
||||||
|
|
||||||
- (id) init
|
- (id) init
|
||||||
{
|
{
|
||||||
if ((self = [super init]))
|
if ((self = [super init]))
|
||||||
|
@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
|
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
|
||||||
{
|
{
|
||||||
id o;
|
id o;
|
||||||
|
|
||||||
o = [[self alloc] init];
|
o = [[self alloc] init];
|
||||||
|
|
||||||
[o setUID: theUID];
|
[o setUID: [NSNumber numberWithInt: [theUID intValue]]];
|
||||||
[o setSequence: theSequence];
|
[o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
|
||||||
|
|
||||||
return [o autorelease];
|
return [o autorelease];
|
||||||
}
|
}
|
||||||
|
@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
- (id) uid
|
- (id) uid
|
||||||
{
|
{
|
||||||
return _uid;
|
return [_uid description];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) setUID: (id) theUID
|
- (void) setUID: (id) theUID
|
||||||
|
@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
- (id) sequence
|
- (id) sequence
|
||||||
{
|
{
|
||||||
return _sequence;
|
return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) setSequence: (id) theSequence
|
- (void) setSequence: (id) theSequence
|
||||||
|
@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
|
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||||
{
|
{
|
||||||
return [[self uid] compare: [theSyncCacheObject uid]];
|
return [self->_uid compare: theSyncCacheObject->_uid];
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
//
|
//
|
||||||
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
|
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||||
{
|
{
|
||||||
if ([[self sequence] isEqual: [NSNull null]] &&
|
if ([self->_sequence isEqual: [NSNull null]] &&
|
||||||
[[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
[theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||||
return [self compareUID: theSyncCacheObject];
|
return [self compareUID: theSyncCacheObject];
|
||||||
|
|
||||||
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||||
return NSOrderedDescending;
|
return NSOrderedDescending;
|
||||||
|
|
||||||
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||||
return NSOrderedAscending;
|
return NSOrderedAscending;
|
||||||
|
|
||||||
// Must check this here, to avoid comparing NSNull objects
|
// Must check this here, to avoid comparing NSNull objects
|
||||||
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame)
|
if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
|
||||||
return [self compareUID: theSyncCacheObject];
|
return [self compareUID: theSyncCacheObject];
|
||||||
|
|
||||||
return [[self sequence] compare: [theSyncCacheObject sequence]];
|
return [self->_sequence compare: theSyncCacheObject->_sequence];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *) description
|
- (NSString *) description
|
||||||
|
@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
// don't send negative reminder - not supported
|
// don't send negative reminder - not supported
|
||||||
if (delta > 0)
|
if (delta > 0)
|
||||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta];
|
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#import <Foundation/NSTimeZone.h>
|
#import <Foundation/NSTimeZone.h>
|
||||||
|
|
||||||
#import <NGExtensions/NSString+misc.h>
|
#import <NGExtensions/NSString+misc.h>
|
||||||
|
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||||
|
#import <NGExtensions/NSObject+Logs.h>
|
||||||
#import <NGObjWeb/WOContext.h>
|
#import <NGObjWeb/WOContext.h>
|
||||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||||
#import <NGObjWeb/WORequest.h>
|
#import <NGObjWeb/WORequest.h>
|
||||||
|
@ -45,11 +47,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#import <NGCards/iCalCalendar.h>
|
#import <NGCards/iCalCalendar.h>
|
||||||
#import <NGCards/iCalDateTime.h>
|
#import <NGCards/iCalDateTime.h>
|
||||||
#import <NGCards/iCalPerson.h>
|
#import <NGCards/iCalPerson.h>
|
||||||
|
#import <NGCards/NSCalendarDate+NGCards.h>
|
||||||
|
#import <NGCards/NSString+NGCards.h>
|
||||||
|
#import <NGCards/iCalCalendar.h>
|
||||||
|
|
||||||
#import <SOGo/SOGoUser.h>
|
#import <SOGo/SOGoUser.h>
|
||||||
#import <SOGo/SOGoUserDefaults.h>
|
#import <SOGo/SOGoUserDefaults.h>
|
||||||
|
#import <SOGo/WORequest+SOGo.h>
|
||||||
|
|
||||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||||
|
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
|
||||||
|
|
||||||
#include "iCalAlarm+ActiveSync.h"
|
#include "iCalAlarm+ActiveSync.h"
|
||||||
#include "iCalRecurrenceRule+ActiveSync.h"
|
#include "iCalRecurrenceRule+ActiveSync.h"
|
||||||
|
@ -98,10 +105,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
else if ([self created])
|
else if ([self created])
|
||||||
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||||
|
|
||||||
|
// Timezone
|
||||||
|
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
|
||||||
|
|
||||||
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
|
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
|
||||||
if ([self startDate])
|
if ([self startDate])
|
||||||
{
|
{
|
||||||
if ([self isAllDay])
|
if ([self isAllDay] && !tz)
|
||||||
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
|
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
|
||||||
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
|
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
|
||||||
hours: 0 minutes: 0
|
hours: 0 minutes: 0
|
||||||
|
@ -114,7 +124,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
|
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
|
||||||
if ([self endDate])
|
if ([self endDate])
|
||||||
{
|
{
|
||||||
if ([self isAllDay])
|
if ([self isAllDay] && !tz)
|
||||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
|
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
|
||||||
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
|
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
|
||||||
hours: 0 minutes: 0
|
hours: 0 minutes: 0
|
||||||
|
@ -124,9 +134,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timezone
|
|
||||||
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
|
|
||||||
|
|
||||||
if (!tz)
|
if (!tz)
|
||||||
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
|
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
|
||||||
|
|
||||||
|
@ -226,9 +233,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
|
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
|
||||||
|
|
||||||
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx
|
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx
|
||||||
if ([[self uid] length])
|
if (![self recurrenceId] && [[self uid] length])
|
||||||
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
|
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
|
||||||
|
|
||||||
// Sensitivity
|
// Sensitivity
|
||||||
if ([[self accessClass] isEqualToString: @"PRIVATE"])
|
if ([[self accessClass] isEqualToString: @"PRIVATE"])
|
||||||
v = 2;
|
v = 2;
|
||||||
|
@ -251,7 +258,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[s appendFormat: @"</Categories>"];
|
[s appendFormat: @"</Categories>"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
|
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
|
||||||
// TODO: improve this to handle more alarm types
|
// TODO: improve this to handle more alarm types
|
||||||
if ([self hasAlarms])
|
if ([self hasAlarms])
|
||||||
|
@ -265,7 +271,74 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// Recurrence rules
|
// Recurrence rules
|
||||||
if ([self isRecurrent])
|
if ([self isRecurrent])
|
||||||
{
|
{
|
||||||
|
NSMutableArray *components, *exdates;
|
||||||
|
iCalEvent *current_component;
|
||||||
|
NSString *recurrence_id;
|
||||||
|
|
||||||
|
unsigned int count, max, i;
|
||||||
|
|
||||||
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
|
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
|
||||||
|
|
||||||
|
components = [NSMutableArray arrayWithArray: [[self parent] events]];
|
||||||
|
max = [components count];
|
||||||
|
|
||||||
|
if (max > 1 || [self hasExceptionDates])
|
||||||
|
{
|
||||||
|
exdates = [NSMutableArray arrayWithArray: [self exceptionDates]];
|
||||||
|
|
||||||
|
[s appendString: @"<Exceptions xmlns=\"Calendar:\">"];
|
||||||
|
|
||||||
|
for (count = 1; count < max; count++)
|
||||||
|
{
|
||||||
|
current_component = [components objectAtIndex: count] ;
|
||||||
|
|
||||||
|
if ([self isAllDay])
|
||||||
|
{
|
||||||
|
recurrence_id = [NSString stringWithFormat: @"%@",
|
||||||
|
[[[current_component recurrenceId] dateByAddingYears: 0 months: 0 days: 0
|
||||||
|
hours: 0 minutes: 0
|
||||||
|
seconds: -[userTimeZone secondsFromGMTForDate:
|
||||||
|
[current_component recurrenceId]]]
|
||||||
|
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recurrence_id = [NSString stringWithFormat: @"%@", [[current_component recurrenceId]
|
||||||
|
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[s appendString: @"<Exception>"];
|
||||||
|
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
|
||||||
|
[s appendFormat: @"%@", [current_component activeSyncRepresentationInContext: context]];
|
||||||
|
[s appendString: @"</Exception>"];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < [exdates count]; i++)
|
||||||
|
{
|
||||||
|
[s appendString: @"<Exception>"];
|
||||||
|
[s appendString: @"<Exception_Deleted>1</Exception_Deleted>"];
|
||||||
|
|
||||||
|
if ([self isAllDay])
|
||||||
|
{
|
||||||
|
recurrence_id = [NSString stringWithFormat: @"%@",
|
||||||
|
[[[[exdates objectAtIndex: i] asCalendarDate] dateByAddingYears: 0 months: 0 days: 0
|
||||||
|
hours: 0 minutes: 0
|
||||||
|
seconds: -[userTimeZone secondsFromGMTForDate:
|
||||||
|
[[exdates objectAtIndex: i] asCalendarDate]]]
|
||||||
|
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recurrence_id = [NSString stringWithFormat: @"%@", [[[exdates objectAtIndex: i] asCalendarDate]
|
||||||
|
activeSyncRepresentationWithoutSeparatorsInContext: context]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
|
||||||
|
[s appendString: @"</Exception>"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[s appendString: @"</Exceptions>"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment
|
// Comment
|
||||||
|
@ -285,13 +358,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
{
|
{
|
||||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
|
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
|
||||||
[s appendFormat: @"<Data>%@</Data>", o];
|
[s appendFormat: @"<Data>%@</Data>", o];
|
||||||
[s appendString: @"</Body>"];
|
[s appendString: @"</Body>"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
|
if (![self recurrenceId])
|
||||||
|
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -337,13 +411,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
||||||
inContext: (WOContext *) context
|
inContext: (WOContext *) context
|
||||||
{
|
{
|
||||||
iCalDateTime *start, *end;
|
iCalDateTime *start, *end; //, *oldstart;
|
||||||
|
NSCalendarDate *oldstart;
|
||||||
NSTimeZone *userTimeZone;
|
NSTimeZone *userTimeZone;
|
||||||
iCalTimeZone *tz;
|
iCalTimeZone *tz;
|
||||||
id o;
|
id o;
|
||||||
|
int deltasecs;
|
||||||
|
|
||||||
BOOL isAllDay;
|
BOOL isAllDay;
|
||||||
|
|
||||||
|
NSMutableArray *occurences;
|
||||||
|
|
||||||
|
occurences = [NSMutableArray arrayWithArray: [[self parent] events]];
|
||||||
|
|
||||||
if ((o = [theValues objectForKey: @"UID"]))
|
if ((o = [theValues objectForKey: @"UID"]))
|
||||||
[self setUid: o];
|
[self setUid: o];
|
||||||
|
|
||||||
|
@ -356,6 +436,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
{
|
{
|
||||||
isAllDay = YES;
|
isAllDay = YES;
|
||||||
}
|
}
|
||||||
|
else if ([occurences count] && !([theValues objectForKey: @"AllDayEvent"]))
|
||||||
|
{
|
||||||
|
// If the occurence has no AllDay tag use it from master.
|
||||||
|
isAllDay = [[occurences objectAtIndex: 0] isAllDay];
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// 0- free, 1- tentative, 2- busy and 3- out of office
|
// 0- free, 1- tentative, 2- busy and 3- out of office
|
||||||
|
@ -422,6 +507,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
{
|
{
|
||||||
o = [o calendarDate];
|
o = [o calendarDate];
|
||||||
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
|
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
|
||||||
|
oldstart = [start dateTime];
|
||||||
[start setTimeZone: tz];
|
[start setTimeZone: tz];
|
||||||
|
|
||||||
if (isAllDay)
|
if (isAllDay)
|
||||||
|
@ -433,6 +519,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
{
|
{
|
||||||
[start setDateTime: o];
|
[start setDateTime: o];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate delta if start date has been changed.
|
||||||
|
if (oldstart)
|
||||||
|
deltasecs = [[start dateTime ] timeIntervalSinceDate: oldstart] * -1;
|
||||||
|
else
|
||||||
|
deltasecs = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((o = [theValues objectForKey: @"EndTime"]))
|
if ((o = [theValues objectForKey: @"EndTime"]))
|
||||||
|
@ -491,6 +583,177 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
RELEASE(rule);
|
RELEASE(rule);
|
||||||
|
|
||||||
[rule takeActiveSyncValues: o inContext: context];
|
[rule takeActiveSyncValues: o inContext: context];
|
||||||
|
|
||||||
|
// Exceptions
|
||||||
|
if ((o = [theValues objectForKey: @"Exceptions"]) && [o isKindOfClass: [NSArray class]])
|
||||||
|
{
|
||||||
|
NSCalendarDate *recurrenceId, *adjustedRecurrenceId, *currentId;
|
||||||
|
NSMutableArray *exdates, *exceptionstouched;
|
||||||
|
iCalEvent *currentOccurence;
|
||||||
|
NSDictionary *exception;
|
||||||
|
|
||||||
|
unsigned int i, count, max;
|
||||||
|
|
||||||
|
[self removeAllExceptionDates];
|
||||||
|
|
||||||
|
exdates = [NSMutableArray array];
|
||||||
|
exceptionstouched = [NSMutableArray array];
|
||||||
|
|
||||||
|
for (i = 0; i < [o count]; i++)
|
||||||
|
{
|
||||||
|
exception = [o objectAtIndex: i];
|
||||||
|
|
||||||
|
if (![[exception objectForKey: @"Exception_Deleted"] intValue])
|
||||||
|
{
|
||||||
|
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
|
||||||
|
|
||||||
|
if (isAllDay)
|
||||||
|
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
|
||||||
|
hours: 0 minutes: 0
|
||||||
|
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
|
||||||
|
|
||||||
|
// When moving the calendar entry (i.e. changing the startDate) iOS and Android sometimes send a value for
|
||||||
|
// Exception_StartTime which is not what we expect.
|
||||||
|
// With this we make sure the recurrenceId is always correct.
|
||||||
|
//
|
||||||
|
// iOS problem - If the master is a no-allday-event but the exception is, an invalid Exception_StartTime is in the request.
|
||||||
|
// e.g. it sends 20150409T040000Z instead of 20150409T060000Z;
|
||||||
|
// This might be a special case since iPhone doesn't allow an allday-exception on a non-allday-event.
|
||||||
|
if (!([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay] minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
|
||||||
|
recurrenceId = [recurrenceId hour:[[start dateTime] hourOfDay] minute:[[start dateTime] minuteOfHour]];
|
||||||
|
|
||||||
|
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
|
||||||
|
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
|
||||||
|
adjustedRecurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
|
||||||
|
hours: 0 minutes: 0 seconds: deltasecs];
|
||||||
|
|
||||||
|
// search for an existing occurence and update it
|
||||||
|
max = [occurences count];
|
||||||
|
currentOccurence = nil;
|
||||||
|
count = 1;
|
||||||
|
while (count < max)
|
||||||
|
{
|
||||||
|
currentOccurence = [occurences objectAtIndex: count] ;
|
||||||
|
currentId = [currentOccurence recurrenceId];
|
||||||
|
|
||||||
|
if ([currentId compare: adjustedRecurrenceId] == NSOrderedSame)
|
||||||
|
{
|
||||||
|
[exceptionstouched addObject: adjustedRecurrenceId];
|
||||||
|
[currentOccurence takeActiveSyncValues: exception inContext: context];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
currentOccurence = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new occurence if we found none to update.
|
||||||
|
if (!currentOccurence)
|
||||||
|
{
|
||||||
|
iCalDateTime *recid;
|
||||||
|
|
||||||
|
currentOccurence = [self mutableCopy];
|
||||||
|
[currentOccurence removeAllRecurrenceRules];
|
||||||
|
[currentOccurence removeAllExceptionRules];
|
||||||
|
[currentOccurence removeAllExceptionDates];
|
||||||
|
|
||||||
|
[[self parent] addToEvents: currentOccurence];
|
||||||
|
[currentOccurence takeActiveSyncValues: exception inContext: context];
|
||||||
|
|
||||||
|
recid = (iCalDateTime *)[currentOccurence uniqueChildWithTag: @"recurrence-id"];
|
||||||
|
if (isAllDay)
|
||||||
|
[recid setDate: adjustedRecurrenceId];
|
||||||
|
else
|
||||||
|
[recid setDateTime: adjustedRecurrenceId];
|
||||||
|
|
||||||
|
[exceptionstouched addObject: [recid dateTime]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ([[exception objectForKey: @"Exception_Deleted"] intValue])
|
||||||
|
{
|
||||||
|
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
|
||||||
|
|
||||||
|
if (isAllDay)
|
||||||
|
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0
|
||||||
|
days: 0 hours: 0 minutes: 0
|
||||||
|
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
|
||||||
|
|
||||||
|
// We add only valid exception dates.
|
||||||
|
if ([self doesOccurOnDate: recurrenceId] &&
|
||||||
|
([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay]
|
||||||
|
minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
|
||||||
|
{
|
||||||
|
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
|
||||||
|
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
|
||||||
|
[exdates addObject: [recurrenceId dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: deltasecs]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update exception dates in master event.
|
||||||
|
max = [exdates count];
|
||||||
|
if (max > 0)
|
||||||
|
{
|
||||||
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
if (([exceptionstouched indexOfObject: [exdates objectAtIndex: count]] == NSNotFound))
|
||||||
|
[self addToExceptionDates: [exdates objectAtIndex: count]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all exceptions included in the request.
|
||||||
|
max = [occurences count];
|
||||||
|
count = 1; // skip the master event
|
||||||
|
while (count < max)
|
||||||
|
{
|
||||||
|
currentOccurence = [occurences objectAtIndex: count] ;
|
||||||
|
currentId = [currentOccurence recurrenceId];
|
||||||
|
|
||||||
|
// Delete all occurences which are not touched (modified/added).
|
||||||
|
if (([exceptionstouched indexOfObject: currentId] == NSNotFound))
|
||||||
|
[[[self parent] children] removeObject: currentOccurence];
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ([self isRecurrent])
|
||||||
|
{
|
||||||
|
// We have no exceptions in request but there is a recurrence rule.
|
||||||
|
// Remove all excpetions and exception dates.
|
||||||
|
iCalEvent *currentOccurence;
|
||||||
|
unsigned int count, max;
|
||||||
|
|
||||||
|
[self removeAllExceptionDates];
|
||||||
|
max = [occurences count];
|
||||||
|
count = 1; // skip the master event
|
||||||
|
while (count < max)
|
||||||
|
{
|
||||||
|
currentOccurence = [occurences objectAtIndex: count];
|
||||||
|
[[[self parent] children] removeObject: currentOccurence];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ([self isRecurrent])
|
||||||
|
{
|
||||||
|
// We have no recurrence rule in request.
|
||||||
|
// Remove all exceptions, exception dates and recurrence rules.
|
||||||
|
iCalEvent *currentOccurence;
|
||||||
|
unsigned int count, max;
|
||||||
|
|
||||||
|
[self removeAllRecurrenceRules];
|
||||||
|
[self removeAllExceptionRules];
|
||||||
|
[self removeAllExceptionDates];
|
||||||
|
|
||||||
|
max = [occurences count];
|
||||||
|
count = 1; // skip the master event
|
||||||
|
while (count < max)
|
||||||
|
{
|
||||||
|
currentOccurence = [occurences objectAtIndex: count];
|
||||||
|
[[[self parent] children] removeObject: currentOccurence];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Organizer - we don't touch the value unless we're the organizer
|
// Organizer - we don't touch the value unless we're the organizer
|
||||||
|
@ -498,7 +761,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o]))
|
([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o]))
|
||||||
{
|
{
|
||||||
iCalPerson *person;
|
iCalPerson *person;
|
||||||
|
|
||||||
person = [iCalPerson elementWithTag: @"organizer"];
|
person = [iCalPerson elementWithTag: @"organizer"];
|
||||||
[person setEmail: o];
|
[person setEmail: o];
|
||||||
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
|
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
|
||||||
|
|
|
@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// Simple reccurrence rule of type "Monthly"
|
// Simple reccurrence rule of type "Monthly"
|
||||||
type = 2;
|
type = 2;
|
||||||
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
|
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
|
||||||
[[[self parent] startDate] dayOfMonth]];
|
(int)[[[self parent] startDate] dayOfMonth]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ([self frequency] == iCalRecurrenceFrequenceYearly)
|
else if ([self frequency] == iCalRecurrenceFrequenceYearly)
|
||||||
|
|
|
@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
{
|
{
|
||||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
|
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
|
||||||
[s appendFormat: @"<Data>%@</Data>", o];
|
[s appendFormat: @"<Data>%@</Data>", o];
|
||||||
[s appendString: @"</Body>"];
|
[s appendString: @"</Body>"];
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ supported by SOGo:
|
||||||
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
|
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
|
||||||
* Community ENTerprise Operating System (CentOS) 5, 6 and 7
|
* Community ENTerprise Operating System (CentOS) 5, 6 and 7
|
||||||
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie)
|
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie)
|
||||||
* Ubuntu 10.04 (Lucid) to 14.04 (Trusty)
|
* Ubuntu 12.04 (Precise) to 14.04 (Trusty)
|
||||||
|
|
||||||
Make sure the required components are started automatically at boot time
|
Make sure the required components are started automatically at boot time
|
||||||
and that they are running before proceeding with the SOGo configuration.
|
and that they are running before proceeding with the SOGo configuration.
|
||||||
|
|
|
@ -233,6 +233,7 @@ Installation
|
||||||
This section will guide you through the installation of the native
|
This section will guide you through the installation of the native
|
||||||
Microsoft Outlook compatibility layer SOGo offers.
|
Microsoft Outlook compatibility layer SOGo offers.
|
||||||
|
|
||||||
|
////
|
||||||
Red Hat Enterprise Linux v6 x86_64
|
Red Hat Enterprise Linux v6 x86_64
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -316,7 +317,7 @@ from this guide.
|
||||||
On Ubuntu 12.04, the Samba init scripts need to be modified to
|
On Ubuntu 12.04, the Samba init scripts need to be modified to
|
||||||
disable the upstart check. For more details, refer to:
|
disable the upstart check. For more details, refer to:
|
||||||
https://wiki.samba.org/index.php/Samba4/InitScript
|
https://wiki.samba.org/index.php/Samba4/InitScript
|
||||||
|
////
|
||||||
|
|
||||||
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
|
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -387,7 +388,7 @@ Run the following commands as root:
|
||||||
|
|
||||||
----
|
----
|
||||||
samba-tool domain provision --realm=example.com \
|
samba-tool domain provision --realm=example.com \
|
||||||
--domain=OPENCHANGE \
|
--domain=EXAMPLE \
|
||||||
--adminpass='%1OpenChange' \
|
--adminpass='%1OpenChange' \
|
||||||
--server-role='domain controller'
|
--server-role='domain controller'
|
||||||
|
|
||||||
|
@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
|
||||||
----
|
----
|
||||||
|
|
||||||
You might consider changing the realm and domain used, to suit your
|
You might consider changing the realm and domain used, to suit your
|
||||||
enviroment.
|
environment.
|
||||||
|
|
||||||
You might also have to
|
You might also have to
|
||||||
remove `/etc/samba/smb.conf` prior running this command.
|
remove `/etc/samba/smb.conf` prior running this command.
|
||||||
|
@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
|
||||||
OpenChange Configuration
|
OpenChange Configuration
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
|
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
|
||||||
|
|
||||||
First, create the OpenChange MySQL user:
|
First, create the OpenChange MySQL user:
|
||||||
|
|
||||||
|
@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
|
||||||
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
|
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
|
||||||
----
|
----
|
||||||
|
|
||||||
|
////
|
||||||
On RHEL, make sure SELinux is disabled:
|
On RHEL, make sure SELinux is disabled:
|
||||||
|
|
||||||
setenforce 0
|
setenforce 0
|
||||||
|
////
|
||||||
|
|
||||||
Next, you can start Samba using the usual command:
|
Next, you can start Samba using the usual command:
|
||||||
|
|
||||||
|
@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
|
||||||
While HTTPS is not required to access this service, it is strongly
|
While HTTPS is not required to access this service, it is strongly
|
||||||
recommended.
|
recommended.
|
||||||
|
|
||||||
|
////
|
||||||
On RHEL-based distributions, the apache configuration required by these
|
On RHEL-based distributions, the apache configuration required by these
|
||||||
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
|
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
|
||||||
`/etc/httpd/conf.d/rpcproxy.conf`.
|
`/etc/httpd/conf.d/rpcproxy.conf`.
|
||||||
|
////
|
||||||
|
|
||||||
For Debian-based distributions, these files can be found
|
For Debian-based distributions, these files can be found
|
||||||
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
|
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
|
||||||
|
@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
|
||||||
a2enconf ocsmanager
|
a2enconf ocsmanager
|
||||||
a2enconf rpcproxy
|
a2enconf rpcproxy
|
||||||
|
|
||||||
|
////
|
||||||
On RHEL-based distributions, make sure the `LoadModule` directive is
|
On RHEL-based distributions, make sure the `LoadModule` directive is
|
||||||
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
|
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
|
||||||
|
////
|
||||||
|
|
||||||
The _reqtimeout_ apache module is known to cause problems when using the
|
The _reqtimeout_ apache module is known to cause problems when using the
|
||||||
default configuration shipped with Debian-based systems. On such
|
default configuration shipped with Debian-based systems. On such
|
||||||
|
@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
|
||||||
a2dismod reqtimeout
|
a2dismod reqtimeout
|
||||||
|
|
||||||
You should now restart the Apache service and make sure it will start on
|
You should now restart the Apache service and make sure it will start on
|
||||||
boot. On RHEL-based distributions, do:
|
boot.
|
||||||
|
////
|
||||||
|
On RHEL-based distributions, do:
|
||||||
|
|
||||||
chkconfig httpd on && /etc/init.d/httpd restart
|
chkconfig httpd on && /etc/init.d/httpd restart
|
||||||
|
////
|
||||||
On Debian-based distributions, do:
|
On Debian-based distributions, do:
|
||||||
|
|
||||||
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
|
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
|
||||||
|
@ -809,11 +818,10 @@ can be ignored for now. This feature is currently not supported.
|
||||||
remove any data associated with the user from the SOGo server and
|
remove any data associated with the user from the SOGo server and
|
||||||
recreate a Microsoft Outlook profile.
|
recreate a Microsoft Outlook profile.
|
||||||
To remove any data associated to a user, use
|
To remove any data associated to a user, use
|
||||||
the `openchange_user_cleanup` script distributed with SOGo. The script
|
the `openchange_user_cleanup` script distributed with OpenChange. The script
|
||||||
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
|
can be found in `/usr/share/openchange/`.
|
||||||
RHEL).
|
|
||||||
To reset a user, run the script as root:
|
To reset a user, run the script as root:
|
||||||
`python openchange_user_cleanup username`. See the usage output for additional options.
|
`openchange_user_cleanup username`. See the usage output for additional options.
|
||||||
* The "Out of Office Assistant" will not currently work. This feature
|
* The "Out of Office Assistant" will not currently work. This feature
|
||||||
has not been implemented.
|
has not been implemented.
|
||||||
* Creating folders below INBOX (when not normally permitted by the IMAP
|
* Creating folders below INBOX (when not normally permitted by the IMAP
|
||||||
|
|
|
@ -267,7 +267,7 @@
|
||||||
NSObject <DOMNodeList> *list;
|
NSObject <DOMNodeList> *list;
|
||||||
NSObject <DOMNode> *valueNode;
|
NSObject <DOMNode> *valueNode;
|
||||||
NSArray *elements;
|
NSArray *elements;
|
||||||
NSString *property, *match;
|
NSString *property=nil, *match=nil;
|
||||||
|
|
||||||
list = [searchElement getElementsByTagName: @"prop"];
|
list = [searchElement getElementsByTagName: @"prop"];
|
||||||
if ([list length])
|
if ([list length])
|
||||||
|
|
88
NEWS
88
NEWS
|
@ -1,13 +1,88 @@
|
||||||
2.3.x (2015-MM-DD)
|
2.3.4 (YYYY-MM-DD)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
New features
|
||||||
|
- Initial support for EAS calendar exceptions
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
- limit the maximum width of toolbar buttons
|
||||||
|
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
- JavaScript exception when printing events from calendars with no assigned color (#3203)
|
||||||
|
- EAS fix for wrong charset being used (#3392)
|
||||||
|
- EAS fix on qp-encoded subjects (#3390)
|
||||||
|
- correctly handle all-day event exceptions when the master event changes
|
||||||
|
- prevent characters in calendar component UID causing issues during import process
|
||||||
|
|
||||||
|
2.3.3a (2015-11-18)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
- expanded mail folders list is not saved (#3386)
|
||||||
|
- cleanup translations
|
||||||
|
|
||||||
|
2.3.3 (2015-11-11)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
New features
|
||||||
|
- initial S/MIME support for EAS (#3327)
|
||||||
|
- now possible to choose which folders to sync over EAS
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS
|
||||||
|
- support for ghosted elements on contacts over EAS
|
||||||
|
- added Macedonian (mk_MK) translation - thanks to Miroslav Jovanovic
|
||||||
|
- added Portuguese (pt) translation - thanks to Eduardo Crispim
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
|
||||||
|
- correctly handle the References header over EAS (#3365)
|
||||||
|
- make sure English is always used when generating Date headers using EAS (#3356)
|
||||||
|
- don't escape quoted strings during versit generation
|
||||||
|
- we now return all cards when we receive an empty addressbook-query REPORT
|
||||||
|
- avoid crash when replying to a mail with no recipients (#3359)
|
||||||
|
- inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271)
|
||||||
|
- prevent postal address showing on single line over EAS (#2614)
|
||||||
|
- display missing events when printing working hours only
|
||||||
|
- fix corner case making server crash when syncing hard deleted messages when clear offline items was set up (Zentyal)
|
||||||
|
- avoid infinite Outlook client loops trying to set read flag when it is already set (Zentyal)
|
||||||
|
- avoid crashing when calendar metadata is missing in the cache (Zentyal)
|
||||||
|
- fix recurrence pattern event corner case created by Mozilla Thunderbird which made server crash (Zentyal)
|
||||||
|
- fix corner case that removes attachments on sending messages from Outlook (Zentyal)
|
||||||
|
- freebusy on web interface works again in multidomain environments (Zentyal)
|
||||||
|
- fix double creation of folders in Outlook when the folder name starts with a digit (Zentyal)
|
||||||
|
- avoid crashing Outlook after setting a custom view in a calendar folder (Zentyal)
|
||||||
|
- handle emails having an attachment as their content
|
||||||
|
- fixed JavaScript syntax error in attendees editor
|
||||||
|
- fixed wrong comparison of meta vs. META tag in HTML mails
|
||||||
|
- fixed popup menu position when moved to the left (#3381)
|
||||||
|
- fixed dialog position when at the bottom of the window (#2646, #3378)
|
||||||
|
- fixed addressbrook-only source entires having a c_uid set
|
||||||
|
|
||||||
|
2.3.2 (2015-09-16)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
|
||||||
|
- improved EAS speed during initial syncing of large mailboxes (#3293)
|
||||||
|
- updated CKEditor to version 4.5.3
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
|
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
|
||||||
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||||
- Fixed several sync issues on environments with multiple users (Zentyal)
|
- fixed several sync issues on environments with multiple users (Zentyal)
|
||||||
- Folders from other users will no longer appear on your Outlook (Zentyal)
|
- folders from other users will no longer appear on your Outlook (Zentyal)
|
||||||
- Use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||||
- Session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
- session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
||||||
|
- less sync issues when setting read flag (Zentyal)
|
||||||
|
- attachments with non-latin filenames sent by Outlook are now received (Zentyal)
|
||||||
|
- support attachments from more mail clients (Zentyal)
|
||||||
|
- avoid conflicting message on saving a draft mail (Zentyal)
|
||||||
|
- less conflicting messages in Outlook while moving messages between folders (Zentyal)
|
||||||
|
- start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344)
|
||||||
|
- fixed localization of calendar categories with empty profile (#3295)
|
||||||
|
- fixed options availability in contextual menu of Contacts module (#3342)
|
||||||
|
|
||||||
2.3.1 (2015-07-23)
|
2.3.1 (2015-07-23)
|
||||||
------------------
|
------------------
|
||||||
|
@ -20,6 +95,7 @@ Enhancements
|
||||||
- added create-folder subcommand to sogo-tool to create contact and calendar folders
|
- added create-folder subcommand to sogo-tool to create contact and calendar folders
|
||||||
- group mail addresses can be used as recipient in Outlook
|
- group mail addresses can be used as recipient in Outlook
|
||||||
- added 'ActiveSync' module constraints
|
- added 'ActiveSync' module constraints
|
||||||
|
- updated CKEditor to version 4.5.1
|
||||||
- added Slovenian translation - thanks to Jens Riecken
|
- added Slovenian translation - thanks to Jens Riecken
|
||||||
- added Chinese (Taiwan) translation
|
- added Chinese (Taiwan) translation
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,9 @@
|
||||||
forURL: (NSURL *) server
|
forURL: (NSURL *) server
|
||||||
forceRenew: (BOOL) renew;
|
forceRenew: (BOOL) renew;
|
||||||
|
|
||||||
|
- (NSString *) passwordInContext: (WOContext *) context;
|
||||||
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#endif /* MAPISTOREAUTHENTICATOR_H */
|
#endif /* MAPISTOREAUTHENTICATOR_H */
|
||||||
|
|
|
@ -72,4 +72,8 @@
|
||||||
return imapPassword;
|
return imapPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *) passwordInContext: (WOContext *) context
|
||||||
|
{
|
||||||
|
return password;
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -671,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
|
||||||
return newAttachment;
|
return newAttachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int) setReadFlag: (uint8_t) flag
|
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||||
{
|
{
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#import <Foundation/NSArray.h>
|
#import <Foundation/NSArray.h>
|
||||||
#import <Foundation/NSCalendarDate.h>
|
#import <Foundation/NSCalendarDate.h>
|
||||||
|
#import <Foundation/NSData.h>
|
||||||
#import <Foundation/NSDictionary.h>
|
#import <Foundation/NSDictionary.h>
|
||||||
#import <Foundation/NSString.h>
|
#import <Foundation/NSString.h>
|
||||||
#import <Foundation/NSValue.h>
|
#import <Foundation/NSValue.h>
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
#import "MAPIStoreDBFolder.h"
|
#import "MAPIStoreDBFolder.h"
|
||||||
#import "MAPIStoreDBMessage.h"
|
#import "MAPIStoreDBMessage.h"
|
||||||
#import "MAPIStoreTypes.h"
|
#import "MAPIStoreTypes.h"
|
||||||
|
#import "NSData+MAPIStore.h"
|
||||||
#import "NSObject+MAPIStore.h"
|
#import "NSObject+MAPIStore.h"
|
||||||
#import "NSString+MAPIStore.h"
|
#import "NSString+MAPIStore.h"
|
||||||
|
|
||||||
|
@ -43,16 +45,28 @@
|
||||||
|
|
||||||
@implementation MAPIStoreDBMessage
|
@implementation MAPIStoreDBMessage
|
||||||
|
|
||||||
+ (int) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
+ (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||||
inMemCtx: (TALLOC_CTX *) memCtx
|
inMemCtx: (TALLOC_CTX *) memCtx
|
||||||
{
|
{
|
||||||
struct SPropTagArray *properties;
|
struct SPropTagArray *properties;
|
||||||
NSUInteger count;
|
NSUInteger count;
|
||||||
enum MAPITAGS faiProperties[] = { 0x68350102, 0x683c0102, 0x683e0102,
|
|
||||||
0x683f0102, 0x68410003, 0x68420102,
|
enum MAPITAGS faiProperties[] = {
|
||||||
0x68450102, 0x68460003,
|
0x68330048, /* PR_VIEW_CLSID */
|
||||||
// PR_VD_NAME_W, PR_VD_FLAGS, PR_VD_VERSION, PR_VIEW_CLSID
|
0x68350102, /* PR_VIEW_STATE */
|
||||||
0x7006001F, 0x70030003, 0x70070003, 0x68330048 };
|
0x683c0102,
|
||||||
|
0x683d0040,
|
||||||
|
0x683e0102,
|
||||||
|
0x683f0102, /* PR_VIEW_VIEWTYPE_KEY */
|
||||||
|
0x68410003,
|
||||||
|
0x68420102,
|
||||||
|
0x68450102,
|
||||||
|
0x68460003,
|
||||||
|
0x7006001F, /* PR_VD_NAME_W */
|
||||||
|
0x70030003, /* PR_VD_FLAGS */
|
||||||
|
0x70070003 /* PR_VD_VERSION */
|
||||||
|
};
|
||||||
|
|
||||||
size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
|
size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
|
||||||
|
|
||||||
properties = talloc_zero (memCtx, struct SPropTagArray);
|
properties = talloc_zero (memCtx, struct SPropTagArray);
|
||||||
|
@ -74,6 +88,13 @@
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
||||||
|
inMemCtx: (TALLOC_CTX *) memCtx
|
||||||
|
{
|
||||||
|
return [[self class] getAvailableProperties: propertiesP
|
||||||
|
inMemCtx: memCtx];
|
||||||
|
}
|
||||||
|
|
||||||
- (id) initWithSOGoObject: (id) newSOGoObject
|
- (id) initWithSOGoObject: (id) newSOGoObject
|
||||||
inContainer: (MAPIStoreObject *) newContainer
|
inContainer: (MAPIStoreObject *) newContainer
|
||||||
{
|
{
|
||||||
|
@ -104,6 +125,105 @@
|
||||||
return objectVersion;
|
return objectVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) _updatePredecessorChangeList
|
||||||
|
{
|
||||||
|
BOOL updated;
|
||||||
|
enum mapistore_error rc;
|
||||||
|
NSData *currentChangeList, *changeKey;
|
||||||
|
NSMutableArray *changeKeys;
|
||||||
|
NSMutableData *newChangeList;
|
||||||
|
NSUInteger count, len;
|
||||||
|
struct SizedXid *changes;
|
||||||
|
struct SPropValue property;
|
||||||
|
struct SRow aRow;
|
||||||
|
struct XID *currentChangeKey;
|
||||||
|
TALLOC_CTX *localMemCtx;
|
||||||
|
uint32_t nChanges;
|
||||||
|
|
||||||
|
localMemCtx = talloc_new (NULL);
|
||||||
|
if (!localMemCtx)
|
||||||
|
{
|
||||||
|
[self errorWithFormat: @"No more memory"];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
|
||||||
|
|
||||||
|
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||||
|
if (!currentChangeList)
|
||||||
|
{
|
||||||
|
/* Create a new PredecessorChangeList */
|
||||||
|
len = [changeKey length];
|
||||||
|
newChangeList = [NSMutableData dataWithCapacity: len + 1];
|
||||||
|
[newChangeList appendUInt8: len];
|
||||||
|
[newChangeList appendData: changeKey];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Update current predecessor change list with new change key */
|
||||||
|
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
|
||||||
|
with: &nChanges];
|
||||||
|
|
||||||
|
updated = NO;
|
||||||
|
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
|
||||||
|
for (count = 0; count < nChanges && !updated; count++)
|
||||||
|
{
|
||||||
|
if (GUID_equal(&changes[count].XID.NameSpaceGuid, ¤tChangeKey->NameSpaceGuid))
|
||||||
|
{
|
||||||
|
NSData *globCnt, *oldGlobCnt;
|
||||||
|
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
|
||||||
|
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
|
||||||
|
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
|
||||||
|
{
|
||||||
|
if ([globCnt length] != [oldGlobCnt length])
|
||||||
|
{
|
||||||
|
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
|
||||||
|
updated = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Serialise it */
|
||||||
|
changeKeys = [NSMutableArray array];
|
||||||
|
|
||||||
|
if (!updated)
|
||||||
|
[changeKeys addObject: changeKey];
|
||||||
|
|
||||||
|
for (count = 0; count < nChanges; count++)
|
||||||
|
{
|
||||||
|
changeKey = [NSData dataWithXID: &changes[count].XID];
|
||||||
|
[changeKeys addObject: changeKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
|
||||||
|
|
||||||
|
newChangeList = [NSMutableData data];
|
||||||
|
len = [changeKeys count];
|
||||||
|
for (count = 0; count < len; count++)
|
||||||
|
{
|
||||||
|
changeKey = [changeKeys objectAtIndex: count];
|
||||||
|
[newChangeList appendUInt8: [changeKey length]];
|
||||||
|
[newChangeList appendData: changeKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([newChangeList length] > 0)
|
||||||
|
{
|
||||||
|
property.ulPropTag = PidTagPredecessorChangeList;
|
||||||
|
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
|
||||||
|
aRow.cValues = 1;
|
||||||
|
aRow.lpProps = &property;
|
||||||
|
rc = [self addPropertiesFromRow: &aRow];
|
||||||
|
if (rc != MAPISTORE_SUCCESS)
|
||||||
|
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
|
||||||
|
}
|
||||||
|
|
||||||
|
talloc_free (localMemCtx);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// FIXME: how this can happen?
|
// FIXME: how this can happen?
|
||||||
//
|
//
|
||||||
|
@ -166,6 +286,9 @@
|
||||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
||||||
forKey: @"version"];
|
forKey: @"version"];
|
||||||
|
|
||||||
|
/* Update PredecessorChangeList accordingly */
|
||||||
|
[self _updatePredecessorChangeList];
|
||||||
|
|
||||||
[self logWithFormat: @"%d props in dict", [properties count]];
|
[self logWithFormat: @"%d props in dict", [properties count]];
|
||||||
|
|
||||||
[sogoObject save];
|
[sogoObject save];
|
||||||
|
@ -209,4 +332,36 @@
|
||||||
return [sogoObject lastModified];
|
return [sogoObject lastModified];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||||
|
{
|
||||||
|
/* Modify PidTagMessageFlags from SetMessageReadFlag and
|
||||||
|
SyncImportReadStateChanges ROPs */
|
||||||
|
NSNumber *flags;
|
||||||
|
uint32_t newFlag;
|
||||||
|
|
||||||
|
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||||
|
if (flags)
|
||||||
|
{
|
||||||
|
newFlag = [flags unsignedLongValue];
|
||||||
|
if (flag & SUPPRESS_RECEIPT)
|
||||||
|
newFlag |= MSGFLAG_READ;
|
||||||
|
if (flag & CLEAR_RN_PENDING)
|
||||||
|
newFlag &= ~MSGFLAG_RN_PENDING;
|
||||||
|
if (flag & CLEAR_READ_FLAG)
|
||||||
|
newFlag &= ~MSGFLAG_READ;
|
||||||
|
if (flag & CLEAR_NRN_PENDING)
|
||||||
|
newFlag &= ~MSGFLAG_NRN_PENDING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newFlag = MSGFLAG_READ;
|
||||||
|
if (flag & CLEAR_READ_FLAG)
|
||||||
|
newFlag = 0x0;
|
||||||
|
}
|
||||||
|
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
|
||||||
|
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||||
|
|
||||||
|
return MAPISTORE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -121,6 +121,7 @@
|
||||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||||
withMIDs: (uint64_t *) targetMids
|
withMIDs: (uint64_t *) targetMids
|
||||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||||
|
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||||
wantCopy: (uint8_t) want_copy
|
wantCopy: (uint8_t) want_copy
|
||||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||||
|
|
||||||
|
|
|
@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||||
withMID: (uint64_t) targetMid
|
withMID: (uint64_t) targetMid
|
||||||
andChangeKey: (struct Binary_r *) targetChangeKey
|
andChangeKey: (struct Binary_r *) targetChangeKey
|
||||||
|
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
|
||||||
wantCopy: (uint8_t) wantCopy
|
wantCopy: (uint8_t) wantCopy
|
||||||
inMemCtx: (TALLOC_CTX *) memCtx
|
inMemCtx: (TALLOC_CTX *) memCtx
|
||||||
{
|
{
|
||||||
|
@ -669,15 +670,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||||
|
|
||||||
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
|
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
|
||||||
|
|
||||||
if (targetChangeKey)
|
if (targetPredecessorChangeList)
|
||||||
{
|
{
|
||||||
property.ulPropTag = PidTagChangeKey;
|
property.ulPropTag = PidTagPredecessorChangeList;
|
||||||
property.value.bin = *targetChangeKey;
|
property.value.bin = *targetPredecessorChangeList;
|
||||||
aRow.cValues = 1;
|
aRow.cValues = 1;
|
||||||
aRow.lpProps = &property;
|
aRow.lpProps = &property;
|
||||||
rc = [destMsg addPropertiesFromRow: &aRow];
|
rc = [destMsg addPropertiesFromRow: &aRow];
|
||||||
if (rc != MAPISTORE_SUCCESS)
|
if (rc != MAPISTORE_SUCCESS)
|
||||||
goto end;
|
{
|
||||||
|
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
[destMsg save: memCtx];
|
[destMsg save: memCtx];
|
||||||
if (!wantCopy)
|
if (!wantCopy)
|
||||||
|
@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||||
withMIDs: (uint64_t *) targetMids
|
withMIDs: (uint64_t *) targetMids
|
||||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||||
|
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||||
wantCopy: (uint8_t) wantCopy
|
wantCopy: (uint8_t) wantCopy
|
||||||
inMemCtx: (TALLOC_CTX *) memCtx
|
inMemCtx: (TALLOC_CTX *) memCtx
|
||||||
{
|
{
|
||||||
|
@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||||
NSString *oldMessageURL;
|
NSString *oldMessageURL;
|
||||||
MAPIStoreMapping *mapping;
|
MAPIStoreMapping *mapping;
|
||||||
SOGoUser *ownerUser;
|
SOGoUser *ownerUser;
|
||||||
struct Binary_r *targetChangeKey;
|
struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
|
||||||
//TALLOC_CTX *memCtx;
|
//TALLOC_CTX *memCtx;
|
||||||
|
|
||||||
//memCtx = talloc_zero (NULL, TALLOC_CTX);
|
//memCtx = talloc_zero (NULL, TALLOC_CTX);
|
||||||
|
@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||||
if (oldMessageURL)
|
if (oldMessageURL)
|
||||||
{
|
{
|
||||||
[oldMessageURLs addObject: oldMessageURL];
|
[oldMessageURLs addObject: oldMessageURL];
|
||||||
if (targetChangeKeys)
|
if (targetChangeKeys && targetPredecessorChangeList)
|
||||||
targetChangeKey = targetChangeKeys[count];
|
{
|
||||||
|
targetChangeKey = targetChangeKeys[count];
|
||||||
|
targetPredecessorChangeList = targetPredecessorChangeLists[count];
|
||||||
|
}
|
||||||
else
|
else
|
||||||
targetChangeKey = NULL;
|
{
|
||||||
|
targetChangeKey = NULL;
|
||||||
|
targetPredecessorChangeList = NULL;
|
||||||
|
}
|
||||||
rc = [self _moveCopyMessageWithMID: srcMids[count]
|
rc = [self _moveCopyMessageWithMID: srcMids[count]
|
||||||
fromFolder: sourceFolder
|
fromFolder: sourceFolder
|
||||||
withMID: targetMids[count]
|
withMID: targetMids[count]
|
||||||
andChangeKey: targetChangeKey
|
andChangeKey: targetChangeKey
|
||||||
|
andPredecessorChangeList: targetPredecessorChangeList
|
||||||
wantCopy: wantCopy
|
wantCopy: wantCopy
|
||||||
inMemCtx: memCtx];
|
inMemCtx: memCtx];
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,8 +41,10 @@
|
||||||
|
|
||||||
/* synchronisation */
|
/* synchronisation */
|
||||||
- (BOOL) synchroniseCache;
|
- (BOOL) synchroniseCache;
|
||||||
|
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
|
||||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||||
withChangeKey: (NSData *) newChangeKey;
|
withChangeKey: (NSData *) oldChangeKey
|
||||||
|
andPredecessorChangeList: (NSData *) pcl;
|
||||||
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
|
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
|
||||||
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
|
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
|
||||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||||
|
|
|
@ -75,6 +75,7 @@ static Class NSNumberK;
|
||||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||||
inContainer: dbFolder]);
|
inContainer: dbFolder]);
|
||||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||||
|
[versionsMessage reloadIfNeeded];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) dealloc
|
- (void) dealloc
|
||||||
|
@ -261,7 +262,6 @@ static Class NSNumberK;
|
||||||
*/
|
*/
|
||||||
- (void) _setChangeKey: (NSData *) changeKey
|
- (void) _setChangeKey: (NSData *) changeKey
|
||||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||||
inChangeListOnly: (BOOL) inChangeListOnly
|
|
||||||
{
|
{
|
||||||
struct XID *xid;
|
struct XID *xid;
|
||||||
NSString *guid;
|
NSString *guid;
|
||||||
|
@ -270,19 +270,15 @@ static Class NSNumberK;
|
||||||
NSMutableDictionary *changeList;
|
NSMutableDictionary *changeList;
|
||||||
|
|
||||||
xid = [changeKey asXIDInMemCtx: NULL];
|
xid = [changeKey asXIDInMemCtx: NULL];
|
||||||
guid = [NSString stringWithGUID: &xid->GUID];
|
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||||
talloc_free (xid);
|
talloc_free (xid);
|
||||||
|
|
||||||
if (!inChangeListOnly)
|
/* 1. set change key association */
|
||||||
{
|
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||||
/* 1. set change key association */
|
globCnt, @"LocalId",
|
||||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
|
nil];
|
||||||
guid, @"GUID",
|
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||||
globCnt, @"LocalId",
|
|
||||||
nil];
|
|
||||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. append/update predecessor change list */
|
/* 2. append/update predecessor change list */
|
||||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||||
|
@ -296,6 +292,77 @@ static Class NSNumberK;
|
||||||
[changeList setObject: globCnt forKey: guid];
|
[changeList setObject: globCnt forKey: guid];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
|
||||||
|
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||||
|
withOldChangeKey: (NSData *) oldChangeKey
|
||||||
|
{
|
||||||
|
NSData *globCnt, *oldGlobCnt;
|
||||||
|
NSDictionary *changeKeyDict;
|
||||||
|
NSString *guid;
|
||||||
|
NSMutableDictionary *changeList;
|
||||||
|
struct SizedXid *sizedXIDList;
|
||||||
|
struct XID xid, *givenChangeKey;
|
||||||
|
TALLOC_CTX *localMemCtx;
|
||||||
|
uint32_t i, length;
|
||||||
|
|
||||||
|
localMemCtx = talloc_new (NULL);
|
||||||
|
if (!localMemCtx)
|
||||||
|
{
|
||||||
|
[self errorWithFormat: @"No more memory"];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predecessorChangeList)
|
||||||
|
{
|
||||||
|
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
|
||||||
|
|
||||||
|
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||||
|
if (!changeList)
|
||||||
|
{
|
||||||
|
changeList = [NSMutableDictionary new];
|
||||||
|
[messageEntry setObject: changeList
|
||||||
|
forKey: @"PredecessorChangeList"];
|
||||||
|
[changeList release];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizedXIDList) {
|
||||||
|
for (i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
xid = sizedXIDList[i].XID;
|
||||||
|
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||||
|
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||||
|
oldGlobCnt = [changeList objectForKey: guid];
|
||||||
|
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||||
|
[changeList setObject: globCnt forKey: guid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldChangeKey)
|
||||||
|
{
|
||||||
|
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
|
||||||
|
if (givenChangeKey) {
|
||||||
|
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
|
||||||
|
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
|
||||||
|
|
||||||
|
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
|
||||||
|
if (!changeKeyDict ||
|
||||||
|
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
|
||||||
|
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
|
||||||
|
{
|
||||||
|
/* The given change key is greater than current one stored in
|
||||||
|
metadata or it does not exist */
|
||||||
|
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||||
|
globCnt, @"LocalId",
|
||||||
|
nil]
|
||||||
|
forKey: @"ChangeKey"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
talloc_free (localMemCtx);
|
||||||
|
}
|
||||||
|
|
||||||
- (EOQualifier *) componentQualifier
|
- (EOQualifier *) componentQualifier
|
||||||
{
|
{
|
||||||
if (!componentQualifier)
|
if (!componentQualifier)
|
||||||
|
@ -465,8 +532,7 @@ static Class NSNumberK;
|
||||||
// A GLOBCNT structure is a 6-byte global namespace counter,
|
// A GLOBCNT structure is a 6-byte global namespace counter,
|
||||||
// we strip the first 2 bytes. The first two bytes is the ReplicaId
|
// we strip the first 2 bytes. The first two bytes is the ReplicaId
|
||||||
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
|
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
|
||||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry
|
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||||
inChangeListOnly: NO];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
now = [NSCalendarDate date];
|
now = [NSCalendarDate date];
|
||||||
|
@ -482,13 +548,123 @@ static Class NSNumberK;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer
|
||||||
|
{
|
||||||
|
/* Try to synchronise old messages in versions.plist cache using an
|
||||||
|
specific c_name. It returns a boolean indicating if the
|
||||||
|
synchronisation was carried out succesfully.
|
||||||
|
|
||||||
|
It should be used as last resort, keeping synchroniseCache as the
|
||||||
|
main sync entry point. */
|
||||||
|
|
||||||
|
uint64_t changeNumber;
|
||||||
|
NSString *changeNumberStr;
|
||||||
|
NSData *changeKey;
|
||||||
|
NSNumber *cLastModified, *cDeleted, *cVersion;
|
||||||
|
EOFetchSpecification *fs;
|
||||||
|
EOQualifier *searchQualifier, *fetchQualifier;
|
||||||
|
NSArray *fetchResults;
|
||||||
|
NSDictionary *result;
|
||||||
|
NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry;
|
||||||
|
GCSFolder *ocsFolder;
|
||||||
|
static NSArray *fields;
|
||||||
|
|
||||||
|
[versionsMessage reloadIfNeeded];
|
||||||
|
currentProperties = [versionsMessage properties];
|
||||||
|
|
||||||
|
messages = [currentProperties objectForKey: @"Messages"];
|
||||||
|
if (!messages)
|
||||||
|
{
|
||||||
|
messages = [NSMutableDictionary new];
|
||||||
|
[currentProperties setObject: messages forKey: @"Messages"];
|
||||||
|
[messages release];
|
||||||
|
}
|
||||||
|
|
||||||
|
messageEntry = [messages objectForKey: nameInContainer];
|
||||||
|
if (!messageEntry)
|
||||||
|
{
|
||||||
|
/* Fetch the message by its name */
|
||||||
|
if (!fields)
|
||||||
|
fields = [[NSArray alloc]
|
||||||
|
initWithObjects: @"c_name", @"c_version", @"c_lastmodified",
|
||||||
|
@"c_deleted", nil];
|
||||||
|
|
||||||
|
searchQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_name"
|
||||||
|
operatorSelector: EOQualifierOperatorEqual
|
||||||
|
value: nameInContainer];
|
||||||
|
fetchQualifier = [[EOAndQualifier alloc]
|
||||||
|
initWithQualifiers: searchQualifier,
|
||||||
|
[self contentComponentQualifier],
|
||||||
|
nil];
|
||||||
|
[fetchQualifier autorelease];
|
||||||
|
[searchQualifier release];
|
||||||
|
|
||||||
|
ocsFolder = [sogoObject ocsFolder];
|
||||||
|
fs = [EOFetchSpecification
|
||||||
|
fetchSpecificationWithEntityName: [ocsFolder folderName]
|
||||||
|
qualifier: fetchQualifier
|
||||||
|
sortOrderings: nil];
|
||||||
|
fetchResults = [ocsFolder fetchFields: fields
|
||||||
|
fetchSpecification: fs
|
||||||
|
ignoreDeleted: NO];
|
||||||
|
|
||||||
|
if ([fetchResults count] == 1)
|
||||||
|
{
|
||||||
|
result = [fetchResults objectAtIndex: 0];
|
||||||
|
cLastModified = [result objectForKey: @"c_lastmodified"];
|
||||||
|
cDeleted = [result objectForKey: @"c_deleted"];
|
||||||
|
if ([cDeleted isKindOfClass: NSNumberK] && [cDeleted intValue])
|
||||||
|
cVersion = [NSNumber numberWithInt: -1];
|
||||||
|
else
|
||||||
|
cVersion = [result objectForKey: @"c_version"];
|
||||||
|
|
||||||
|
changeNumber = [[self context] getNewChangeNumber];
|
||||||
|
changeNumberStr = [NSString stringWithUnsignedLongLong: changeNumber];
|
||||||
|
|
||||||
|
/* Create new message entry in Messages dict */
|
||||||
|
messageEntry = [NSMutableDictionary new];
|
||||||
|
[messages setObject: messageEntry forKey: nameInContainer];
|
||||||
|
[messageEntry release];
|
||||||
|
|
||||||
|
/* Store cLastModified, cVersion and the change number */
|
||||||
|
[messageEntry setObject: cLastModified forKey: @"c_lastmodified"];
|
||||||
|
[messageEntry setObject: cVersion forKey: @"c_version"];
|
||||||
|
[messageEntry setObject: changeNumberStr forKey: @"version"];
|
||||||
|
|
||||||
|
/* Store the change key */
|
||||||
|
changeKey = [self getReplicaKeyFromGlobCnt: changeNumber >> 16];
|
||||||
|
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||||
|
|
||||||
|
/* Store the changeNumber -> cLastModified mapping */
|
||||||
|
mapping = [currentProperties objectForKey: @"VersionMapping"];
|
||||||
|
if (!mapping)
|
||||||
|
{
|
||||||
|
mapping = [NSMutableDictionary new];
|
||||||
|
[currentProperties setObject: mapping forKey: @"VersionMapping"];
|
||||||
|
[mapping release];
|
||||||
|
}
|
||||||
|
[mapping setObject: cLastModified forKey: changeNumberStr];
|
||||||
|
|
||||||
|
/* Save the message */
|
||||||
|
[versionsMessage save];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If message entry exists, then synchroniseCache did its job */
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||||
withChangeKey: (NSData *) newChangeKey
|
withChangeKey: (NSData *) oldChangeKey
|
||||||
|
andPredecessorChangeList: (NSData *) pcl
|
||||||
{
|
{
|
||||||
NSMutableDictionary *messages, *messageEntry;
|
NSMutableDictionary *messages, *messageEntry;
|
||||||
|
|
||||||
[self synchroniseCache];
|
[self synchroniseCache];
|
||||||
if (newChangeKey)
|
if (oldChangeKey || pcl)
|
||||||
{
|
{
|
||||||
messages = [[versionsMessage properties] objectForKey: @"Messages"];
|
messages = [[versionsMessage properties] objectForKey: @"Messages"];
|
||||||
messageEntry = [messages objectForKey: messageKey];
|
messageEntry = [messages objectForKey: messageKey];
|
||||||
|
@ -496,8 +672,8 @@ static Class NSNumberK;
|
||||||
[NSException raise: @"MAPIStoreIOException"
|
[NSException raise: @"MAPIStoreIOException"
|
||||||
format: @"no version record found for message '%@'",
|
format: @"no version record found for message '%@'",
|
||||||
messageKey];
|
messageKey];
|
||||||
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
|
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
|
||||||
inChangeListOnly: YES];
|
withOldChangeKey: oldChangeKey];
|
||||||
[versionsMessage save];
|
[versionsMessage save];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,8 @@
|
||||||
{
|
{
|
||||||
uint64_t version = ULLONG_MAX;
|
uint64_t version = ULLONG_MAX;
|
||||||
NSString *changeNumber;
|
NSString *changeNumber;
|
||||||
|
BOOL synced;
|
||||||
|
|
||||||
if (!isNew)
|
if (!isNew)
|
||||||
{
|
{
|
||||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||||
|
@ -189,16 +190,28 @@
|
||||||
{
|
{
|
||||||
[self warnWithFormat: @"attempting to get change number"
|
[self warnWithFormat: @"attempting to get change number"
|
||||||
@" by synchronising folder..."];
|
@" by synchronising folder..."];
|
||||||
[(MAPIStoreGCSFolder *) container synchroniseCache];
|
synced = [(MAPIStoreGCSFolder *) container synchroniseCache];
|
||||||
changeNumber = [(MAPIStoreGCSFolder *) container
|
if (synced)
|
||||||
changeNumberForMessageWithKey: [self nameInContainer]];
|
|
||||||
|
|
||||||
if (changeNumber)
|
|
||||||
[self logWithFormat: @"got one"];
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
[self errorWithFormat: @"still nothing. We crash!"];
|
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||||
abort();
|
changeNumberForMessageWithKey: [self nameInContainer]];
|
||||||
|
}
|
||||||
|
if (!changeNumber)
|
||||||
|
{
|
||||||
|
[self warnWithFormat: @"attempting to get change number"
|
||||||
|
@" by synchronising this specific message..."];
|
||||||
|
synced = [(MAPIStoreGCSFolder *) container
|
||||||
|
synchroniseCacheFor: [self nameInContainer]];
|
||||||
|
if (synced)
|
||||||
|
{
|
||||||
|
changeNumber = [(MAPIStoreGCSFolder *) container
|
||||||
|
changeNumberForMessageWithKey: [self nameInContainer]];
|
||||||
|
}
|
||||||
|
if (!changeNumber)
|
||||||
|
{
|
||||||
|
[self errorWithFormat: @"still nothing. We crash!"];
|
||||||
|
abort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
version = [changeNumber unsignedLongLongValue] >> 16;
|
version = [changeNumber unsignedLongLongValue] >> 16;
|
||||||
|
@ -209,13 +222,16 @@
|
||||||
|
|
||||||
- (void) updateVersions
|
- (void) updateVersions
|
||||||
{
|
{
|
||||||
NSData *newChangeKey;
|
/* Update ChangeKey and PredecessorChangeList on message's save */
|
||||||
|
NSData *newChangeKey, *predecessorChangeList;
|
||||||
|
|
||||||
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||||
|
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
|
||||||
|
|
||||||
[(MAPIStoreGCSFolder *) container
|
[(MAPIStoreGCSFolder *) container
|
||||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||||
withChangeKey: newChangeKey];
|
withChangeKey: newChangeKey
|
||||||
|
andPredecessorChangeList: predecessorChangeList];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#import <SOGo/NSArray+Utilities.h>
|
#import <SOGo/NSArray+Utilities.h>
|
||||||
#import <Mailer/SOGoMailBodyPart.h>
|
#import <Mailer/SOGoMailBodyPart.h>
|
||||||
#import <Mailer/SOGoMailObject.h>
|
#import <Mailer/SOGoMailObject.h>
|
||||||
|
#import <Mailer/NSDictionary+Mail.h>
|
||||||
|
|
||||||
#import "MAPIStoreTypes.h"
|
#import "MAPIStoreTypes.h"
|
||||||
#import "MAPIStoreMailMessage.h"
|
#import "MAPIStoreMailMessage.h"
|
||||||
|
@ -108,7 +109,7 @@
|
||||||
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
|
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
|
||||||
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
|
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
|
||||||
0x68};
|
0x68};
|
||||||
|
|
||||||
*data = [[NSData dataWithBytes: recordBytes length: 16]
|
*data = [[NSData dataWithBytes: recordBytes length: 16]
|
||||||
asBinaryInMemCtx: memCtx];
|
asBinaryInMemCtx: memCtx];
|
||||||
|
|
||||||
|
@ -117,19 +118,7 @@
|
||||||
|
|
||||||
- (NSString *) _fileName
|
- (NSString *) _fileName
|
||||||
{
|
{
|
||||||
NSString *fileName;
|
return [bodyInfo filename];
|
||||||
NSDictionary *parameters;
|
|
||||||
|
|
||||||
fileName = [[bodyInfo objectForKey: @"parameterList"]
|
|
||||||
objectForKey: @"name"];
|
|
||||||
if (!fileName)
|
|
||||||
{
|
|
||||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
|
||||||
objectForKey: @"parameterList"];
|
|
||||||
fileName = [parameters objectForKey: @"filename"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int) getPidTagAttachLongFilename: (void **) data
|
- (int) getPidTagAttachLongFilename: (void **) data
|
||||||
|
@ -178,7 +167,7 @@
|
||||||
|
|
||||||
- (int) getPidTagAttachContentId: (void **) data
|
- (int) getPidTagAttachContentId: (void **) data
|
||||||
inMemCtx: (TALLOC_CTX *) memCtx
|
inMemCtx: (TALLOC_CTX *) memCtx
|
||||||
{
|
{
|
||||||
*data = [[bodyInfo objectForKey: @"bodyId"]
|
*data = [[bodyInfo objectForKey: @"bodyId"]
|
||||||
asUnicodeInMemCtx: memCtx];
|
asUnicodeInMemCtx: memCtx];
|
||||||
|
|
||||||
|
|
|
@ -160,23 +160,25 @@ MakeDisplayFolderName (NSString *folderName)
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
{
|
{
|
||||||
context = talloc_zero (memCtx, struct mapistore_contexts_list);
|
context = talloc_zero (memCtx, struct mapistore_contexts_list);
|
||||||
// secondaryFolders has the names (1) Imap4Encoded and (2) asCSSIdentifier
|
// secondaryFolders has the names (1) Imap4Encoded ,(2) asCSSIdentifier and (3) "folder"-prefixed
|
||||||
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
|
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
|
||||||
currentName = [secondaryFolders objectAtIndex: count];
|
currentName = [secondaryFolders objectAtIndex: count];
|
||||||
// To get the real name we have to revert that (applying the decode functions)
|
// To get the real name we have to revert that (applying the decode functions
|
||||||
// in reverse order
|
// in reverse order)
|
||||||
// e.g.: Problèmes de synchronisation
|
// e.g.: Problèmes de synchronisation
|
||||||
realName = [[currentName fromCSSIdentifier]
|
realName = [[[currentName substringFromIndex: 6]
|
||||||
stringByDecodingImap4FolderName];
|
fromCSSIdentifier]
|
||||||
|
stringByDecodingImap4FolderName];
|
||||||
// And finally to represent that as URI we have to (1) asCSSIdentifier,
|
// And finally to represent that as URI we have to (1) asCSSIdentifier,
|
||||||
// (2) Imap4Encode and (3) AddPercentEscapes
|
// (2) Imap4Encode (3) AddPercentEscapes and (4) add the "folder" prefix
|
||||||
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
|
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
|
||||||
// In the example there are no percent escapes added because is already ok
|
// In the example there are no percent escapes added because is already ok
|
||||||
stringData = [[[realName asCSSIdentifier]
|
stringData = [[[realName asCSSIdentifier]
|
||||||
stringByEncodingImap4FolderName]
|
stringByEncodingImap4FolderName]
|
||||||
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
|
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
|
||||||
|
stringData = [NSString stringWithFormat: @"folder%@", stringData];
|
||||||
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context];
|
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context];
|
||||||
context->name = [[realName substringFromIndex: 6] asUnicodeInMemCtx: context];
|
context->name = [realName asUnicodeInMemCtx: context];
|
||||||
context->main_folder = false;
|
context->main_folder = false;
|
||||||
context->role = MAPISTORE_MAIL_ROLE;
|
context->role = MAPISTORE_MAIL_ROLE;
|
||||||
context->tag = "tag";
|
context->tag = "tag";
|
||||||
|
@ -200,7 +202,7 @@ MakeDisplayFolderName (NSString *folderName)
|
||||||
andTDBIndexing: NULL];
|
andTDBIndexing: NULL];
|
||||||
accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
|
accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
|
||||||
folderName = [NSString stringWithFormat: @"folder%@",
|
folderName = [NSString stringWithFormat: @"folder%@",
|
||||||
[newFolderName asCSSIdentifier]];
|
[[newFolderName stringByEncodingImap4FolderName] asCSSIdentifier]];
|
||||||
newFolder = [SOGoMailFolder objectWithName: folderName
|
newFolder = [SOGoMailFolder objectWithName: folderName
|
||||||
inContainer: accountFolder];
|
inContainer: accountFolder];
|
||||||
if ([newFolder create])
|
if ([newFolder create])
|
||||||
|
@ -209,7 +211,7 @@ MakeDisplayFolderName (NSString *folderName)
|
||||||
withString: @"%40"],
|
withString: @"%40"],
|
||||||
[userName stringByReplacingOccurrencesOfString: @"@"
|
[userName stringByReplacingOccurrencesOfString: @"@"
|
||||||
withString: @"%40"],
|
withString: @"%40"],
|
||||||
[[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
|
[folderName stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
|
||||||
else
|
else
|
||||||
mapistoreURI = nil;
|
mapistoreURI = nil;
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,8 @@
|
||||||
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
|
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
|
||||||
- (void) setChangeKey: (NSData *) changeKey
|
- (void) setChangeKey: (NSData *) changeKey
|
||||||
forMessageWithKey: (NSString *) messageKey;
|
forMessageWithKey: (NSString *) messageKey;
|
||||||
|
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||||
|
forMessageWithKey: (NSString *) messageKey;
|
||||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||||
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
|
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,8 @@
|
||||||
|
|
||||||
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||||
|
|
||||||
|
#include <gen_ndr/exchange.h>
|
||||||
|
|
||||||
#undef DEBUG
|
#undef DEBUG
|
||||||
#include <util/attr.h>
|
#include <util/attr.h>
|
||||||
#include <libmapi/libmapi.h>
|
#include <libmapi/libmapi.h>
|
||||||
|
@ -109,6 +111,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||||
inContainer: dbFolder]);
|
inContainer: dbFolder]);
|
||||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||||
|
[versionsMessage reloadIfNeeded];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) ensureFolderExists
|
- (BOOL) ensureFolderExists
|
||||||
|
@ -516,6 +519,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
return [modseq1 compare: modseq2];
|
return [modseq1 compare: modseq2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) _updatePredecessorChangeListWith: (NSData *) predecessorChangeList
|
||||||
|
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||||
|
{
|
||||||
|
NSData *globCnt, *oldGlobCnt;
|
||||||
|
NSMutableDictionary *changeList;
|
||||||
|
NSString *guid;
|
||||||
|
struct SizedXid *sizedXIDList;
|
||||||
|
struct XID xid;
|
||||||
|
uint32_t i, length;
|
||||||
|
|
||||||
|
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: NULL with: &length];
|
||||||
|
|
||||||
|
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||||
|
if (!changeList)
|
||||||
|
{
|
||||||
|
changeList = [NSMutableDictionary new];
|
||||||
|
[messageEntry setObject: changeList
|
||||||
|
forKey: @"PredecessorChangeList"];
|
||||||
|
[changeList release];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizedXIDList) {
|
||||||
|
for (i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
xid = sizedXIDList[i].XID;
|
||||||
|
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||||
|
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||||
|
oldGlobCnt = [changeList objectForKey: guid];
|
||||||
|
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||||
|
[changeList setObject: globCnt forKey: guid];
|
||||||
|
}
|
||||||
|
|
||||||
|
talloc_free (sizedXIDList);
|
||||||
|
}
|
||||||
|
|
||||||
|
[versionsMessage save];
|
||||||
|
}
|
||||||
|
|
||||||
- (void) _setChangeKey: (NSData *) changeKey
|
- (void) _setChangeKey: (NSData *) changeKey
|
||||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||||
{
|
{
|
||||||
|
@ -526,8 +567,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
NSMutableDictionary *changeList;
|
NSMutableDictionary *changeList;
|
||||||
|
|
||||||
xid = [changeKey asXIDInMemCtx: NULL];
|
xid = [changeKey asXIDInMemCtx: NULL];
|
||||||
guid = [NSString stringWithGUID: &xid->GUID];
|
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||||
talloc_free (xid);
|
talloc_free (xid);
|
||||||
|
|
||||||
/* 1. set change key association */
|
/* 1. set change key association */
|
||||||
|
@ -553,8 +594,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
{
|
{
|
||||||
BOOL rc = YES;
|
BOOL rc = YES;
|
||||||
uint64_t newChangeNum;
|
uint64_t newChangeNum;
|
||||||
NSNumber *ti, *modseq, *initialLastModseq, *lastModseq,
|
NSNumber *ti, *modseq, *lastModseq, *nextModseq;
|
||||||
*nextModseq;
|
|
||||||
NSString *changeNumber, *uid, *messageKey;
|
NSString *changeNumber, *uid, *messageKey;
|
||||||
uint64_t lastModseqNbr;
|
uint64_t lastModseqNbr;
|
||||||
EOQualifier *searchQualifier;
|
EOQualifier *searchQualifier;
|
||||||
|
@ -593,7 +633,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
|
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
|
||||||
initialLastModseq = lastModseq;
|
|
||||||
if (lastModseq)
|
if (lastModseq)
|
||||||
{
|
{
|
||||||
lastModseqNbr = [lastModseq unsignedLongLongValue];
|
lastModseqNbr = [lastModseq unsignedLongLongValue];
|
||||||
|
@ -677,40 +716,37 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. we synchronise expunged UIDs */
|
/* 2. we synchronise expunged UIDs */
|
||||||
if (initialLastModseq)
|
fetchResults = [(SOGoMailFolder *) sogoObject
|
||||||
|
fetchUIDsOfVanishedItems: lastModseqNbr];
|
||||||
|
|
||||||
|
max = [fetchResults count];
|
||||||
|
|
||||||
|
changeNumber = nil;
|
||||||
|
for (count = 0; count < max; count++)
|
||||||
{
|
{
|
||||||
fetchResults = [(SOGoMailFolder *) sogoObject
|
uid = [[fetchResults objectAtIndex: count] stringValue];
|
||||||
fetchUIDsOfVanishedItems: lastModseqNbr];
|
if ([messages objectForKey: uid])
|
||||||
|
|
||||||
max = [fetchResults count];
|
|
||||||
|
|
||||||
changeNumber = nil;
|
|
||||||
for (count = 0; count < max; count++)
|
|
||||||
{
|
{
|
||||||
uid = [[fetchResults objectAtIndex: count] stringValue];
|
if (!changeNumber)
|
||||||
if ([messages objectForKey: uid])
|
|
||||||
{
|
{
|
||||||
if (!changeNumber)
|
newChangeNum = [[self context] getNewChangeNumber];
|
||||||
{
|
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
|
||||||
newChangeNum = [[self context] getNewChangeNumber];
|
|
||||||
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
|
|
||||||
}
|
|
||||||
[messages removeObjectForKey: uid];
|
|
||||||
[self logWithFormat: @"Removed message entry for UID %@", uid];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
[self logWithFormat:@"Message entry not found for UID %@", uid];
|
|
||||||
}
|
}
|
||||||
|
[messages removeObjectForKey: uid];
|
||||||
|
[self logWithFormat: @"Removed message entry for UID %@", uid];
|
||||||
}
|
}
|
||||||
if (changeNumber)
|
else
|
||||||
{
|
{
|
||||||
[currentProperties setObject: changeNumber
|
[self logWithFormat:@"Message entry not found for UID %@", uid];
|
||||||
forKey: @"SyncLastDeleteChangeNumber"];
|
|
||||||
[mapping setObject: lastModseq forKey: changeNumber];
|
|
||||||
foundChange = YES;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (changeNumber)
|
||||||
|
{
|
||||||
|
[currentProperties setObject: changeNumber
|
||||||
|
forKey: @"SyncLastDeleteChangeNumber"];
|
||||||
|
[mapping setObject: lastModseq forKey: changeNumber];
|
||||||
|
foundChange = YES;
|
||||||
|
}
|
||||||
|
|
||||||
if (foundChange)
|
if (foundChange)
|
||||||
{
|
{
|
||||||
|
@ -924,8 +960,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
return changeNumber;
|
return changeNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) setChangeKey: (NSData *) changeKey
|
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
|
||||||
forMessageWithKey: (NSString *) messageKey
|
|
||||||
{
|
{
|
||||||
NSMutableDictionary *messages, *messageEntry;
|
NSMutableDictionary *messages, *messageEntry;
|
||||||
NSString *messageUid;
|
NSString *messageUid;
|
||||||
|
@ -936,7 +971,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
messageEntry = [messages objectForKey: messageUid];
|
messageEntry = [messages objectForKey: messageUid];
|
||||||
if (!messageEntry)
|
if (!messageEntry)
|
||||||
{
|
{
|
||||||
[self warnWithFormat: @"attempting to synchronise to set the change key for "
|
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
|
||||||
@"this message %@", messageKey];
|
@"this message %@", messageKey];
|
||||||
synced = [self synchroniseCacheForUID: messageUid];
|
synced = [self synchroniseCacheForUID: messageUid];
|
||||||
if (synced)
|
if (synced)
|
||||||
|
@ -947,11 +982,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
abort ();
|
abort ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
|
||||||
|
return messageEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setChangeKey: (NSData *) changeKey
|
||||||
|
forMessageWithKey: (NSString *) messageKey
|
||||||
|
{
|
||||||
|
[self _setChangeKey: changeKey
|
||||||
|
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||||
|
|
||||||
[versionsMessage save];
|
[versionsMessage save];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||||
|
forMessageWithKey: (NSString *) messageKey
|
||||||
|
{
|
||||||
|
/* Update predecessor change list property given the change key. It
|
||||||
|
returns if the change key has been added to the list or not */
|
||||||
|
BOOL added = NO;
|
||||||
|
NSData *globCnt, *oldGlobCnt;
|
||||||
|
NSDictionary *messageEntry;
|
||||||
|
NSMutableDictionary *changeList;
|
||||||
|
NSString *guid;
|
||||||
|
struct XID *xid;
|
||||||
|
|
||||||
|
xid = [changeKey asXIDInMemCtx: NULL];
|
||||||
|
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||||
|
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||||
|
talloc_free (xid);
|
||||||
|
|
||||||
|
messageEntry = [self _messageEntryFromMessageKey: messageKey];
|
||||||
|
if (messageEntry)
|
||||||
|
{
|
||||||
|
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||||
|
if (changeList)
|
||||||
|
{
|
||||||
|
oldGlobCnt = [changeList objectForKey: guid];
|
||||||
|
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||||
|
{
|
||||||
|
[changeList setObject: globCnt forKey: guid];
|
||||||
|
[versionsMessage save];
|
||||||
|
added = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
[self errorWithFormat: @"Missing predecessor change list to update"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
|
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
|
||||||
{
|
{
|
||||||
NSDictionary *messages, *changeKeyDict;
|
NSDictionary *messages, *changeKeyDict;
|
||||||
|
@ -1217,6 +1298,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||||
withMIDs: (uint64_t *) targetMids
|
withMIDs: (uint64_t *) targetMids
|
||||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||||
|
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||||
wantCopy: (uint8_t) wantCopy
|
wantCopy: (uint8_t) wantCopy
|
||||||
inMemCtx: (TALLOC_CTX *) memCtx
|
inMemCtx: (TALLOC_CTX *) memCtx
|
||||||
|
|
||||||
|
@ -1231,12 +1313,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||||
NSDictionary *result;
|
NSDictionary *result;
|
||||||
NSUInteger count;
|
NSUInteger count;
|
||||||
NSArray *a;
|
NSArray *a;
|
||||||
NSData *changeKey;
|
NSData *changeList;
|
||||||
|
|
||||||
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
|
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
|
||||||
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
|
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
|
||||||
fromFolder: sourceFolder withMIDs: targetMids
|
fromFolder: sourceFolder withMIDs: targetMids
|
||||||
andChangeKeys: targetChangeKeys
|
andChangeKeys: targetChangeKeys
|
||||||
|
andPredecessorChangeLists: targetPredecessorChangeLists
|
||||||
wantCopy: wantCopy
|
wantCopy: wantCopy
|
||||||
inMemCtx: memCtx];
|
inMemCtx: memCtx];
|
||||||
|
|
||||||
|
@ -1325,11 +1408,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||||
[self synchroniseCache];
|
[self synchroniseCache];
|
||||||
for (count = 0; count < midCount; count++)
|
for (count = 0; count < midCount; count++)
|
||||||
{
|
{
|
||||||
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
|
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
|
||||||
messageKey = [NSString stringWithFormat: @"%@.eml",
|
messageKey = [NSString stringWithFormat: @"%@.eml",
|
||||||
[destUIDs objectAtIndex: count]];
|
[destUIDs objectAtIndex: count]];
|
||||||
[self setChangeKey: changeKey
|
[self _updatePredecessorChangeListWith: changeList
|
||||||
forMessageWithKey: messageKey];
|
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#import <Mailer/NSData+Mail.h>
|
#import <Mailer/NSData+Mail.h>
|
||||||
#import <Mailer/SOGoMailBodyPart.h>
|
#import <Mailer/SOGoMailBodyPart.h>
|
||||||
#import <Mailer/SOGoMailObject.h>
|
#import <Mailer/SOGoMailObject.h>
|
||||||
|
#import <Mailer/NSDictionary+Mail.h>
|
||||||
|
|
||||||
#import "Codepages.h"
|
#import "Codepages.h"
|
||||||
#import "NSData+MAPIStore.h"
|
#import "NSData+MAPIStore.h"
|
||||||
|
@ -196,7 +197,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
count1 = [keys indexOfObject: data1];
|
count1 = [keys indexOfObject: data1];
|
||||||
data2 = [entry2 objectForKey: @"mimeType"];
|
data2 = [entry2 objectForKey: @"mimeType"];
|
||||||
count2 = [keys indexOfObject: data2];
|
count2 = [keys indexOfObject: data2];
|
||||||
|
|
||||||
if (count1 == count2)
|
if (count1 == count2)
|
||||||
{
|
{
|
||||||
data1 = [entry1 objectForKey: @"key"];
|
data1 = [entry1 objectForKey: @"key"];
|
||||||
|
@ -529,7 +530,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
else
|
else
|
||||||
stringValue = @"";
|
stringValue = @"";
|
||||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||||
|
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,7 +625,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
NSDictionary *coreInfos;
|
NSDictionary *coreInfos;
|
||||||
NSArray *flags;
|
NSArray *flags;
|
||||||
unsigned int v = 0;
|
unsigned int v = 0;
|
||||||
|
|
||||||
coreInfos = [sogoObject fetchCoreInfos];
|
coreInfos = [sogoObject fetchCoreInfos];
|
||||||
flags = [coreInfos objectForKey: @"flags"];
|
flags = [coreInfos objectForKey: @"flags"];
|
||||||
|
|
||||||
|
@ -636,7 +637,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
if ([[self attachmentKeys]
|
if ([[self attachmentKeys]
|
||||||
count] > 0)
|
count] > 0)
|
||||||
v |= MSGFLAG_HASATTACH;
|
v |= MSGFLAG_HASATTACH;
|
||||||
|
|
||||||
*data = MAPILongValue (memCtx, v);
|
*data = MAPILongValue (memCtx, v);
|
||||||
|
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
|
@ -656,7 +657,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
v = 2;
|
v = 2;
|
||||||
else
|
else
|
||||||
v = 0;
|
v = 0;
|
||||||
|
|
||||||
*data = MAPILongValue (memCtx, v);
|
*data = MAPILongValue (memCtx, v);
|
||||||
|
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
|
@ -668,15 +669,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
NSDictionary *coreInfos;
|
NSDictionary *coreInfos;
|
||||||
NSArray *flags;
|
NSArray *flags;
|
||||||
unsigned int v;
|
unsigned int v;
|
||||||
|
|
||||||
coreInfos = [sogoObject fetchCoreInfos];
|
coreInfos = [sogoObject fetchCoreInfos];
|
||||||
|
|
||||||
flags = [coreInfos objectForKey: @"flags"];
|
flags = [coreInfos objectForKey: @"flags"];
|
||||||
if ([flags containsObject: @"flagged"])
|
if ([flags containsObject: @"flagged"])
|
||||||
v = 6;
|
v = 6;
|
||||||
else
|
else
|
||||||
v = 0;
|
v = 0;
|
||||||
|
|
||||||
*data = MAPILongValue (memCtx, v);
|
*data = MAPILongValue (memCtx, v);
|
||||||
|
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
|
@ -755,7 +756,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
||||||
{
|
{
|
||||||
cn = [ngAddress displayName];
|
cn = [ngAddress displayName];
|
||||||
|
|
||||||
// If we don't have a displayName, we use the email address instead. This
|
// If we don't have a displayName, we use the email address instead. This
|
||||||
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
|
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
|
||||||
// nor in the recipient field if we reply to the email.
|
// nor in the recipient field if we reply to the email.
|
||||||
|
@ -809,7 +810,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
entryId = MAPIStoreExternalEntryId (cn, email);
|
entryId = MAPIStoreExternalEntryId (cn, email);
|
||||||
|
|
||||||
*data = [entryId asBinaryInMemCtx: memCtx];
|
*data = [entryId asBinaryInMemCtx: memCtx];
|
||||||
|
|
||||||
rc = MAPISTORE_SUCCESS;
|
rc = MAPISTORE_SUCCESS;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -966,15 +967,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
{
|
{
|
||||||
uint32_t v;
|
uint32_t v;
|
||||||
NSString *s;
|
NSString *s;
|
||||||
|
|
||||||
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
|
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
|
||||||
v = 0x1;
|
v = 0x1;
|
||||||
|
|
||||||
if ([s hasPrefix: @"1"]) v = 0x2;
|
if ([s hasPrefix: @"1"]) v = 0x2;
|
||||||
else if ([s hasPrefix: @"2"]) v = 0x2;
|
else if ([s hasPrefix: @"2"]) v = 0x2;
|
||||||
else if ([s hasPrefix: @"4"]) v = 0x0;
|
else if ([s hasPrefix: @"4"]) v = 0x0;
|
||||||
else if ([s hasPrefix: @"5"]) v = 0x0;
|
else if ([s hasPrefix: @"5"]) v = 0x0;
|
||||||
|
|
||||||
*data = MAPILongValue (memCtx, v);
|
*data = MAPILongValue (memCtx, v);
|
||||||
|
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
|
@ -1163,14 +1164,14 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
|
|
||||||
if (!headerSetup)
|
if (!headerSetup)
|
||||||
[self _fetchHeaderData];
|
[self _fetchHeaderData];
|
||||||
|
|
||||||
if ([headerMimeType isEqualToString: @"text/plain"])
|
if ([headerMimeType isEqualToString: @"text/plain"])
|
||||||
format = EDITOR_FORMAT_PLAINTEXT;
|
format = EDITOR_FORMAT_PLAINTEXT;
|
||||||
else if ([headerMimeType isEqualToString: @"text/html"])
|
else if ([headerMimeType isEqualToString: @"text/html"])
|
||||||
format = EDITOR_FORMAT_HTML;
|
format = EDITOR_FORMAT_HTML;
|
||||||
else
|
else
|
||||||
format = 0; /* EDITOR_FORMAT_DONTKNOW */
|
format = 0; /* EDITOR_FORMAT_DONTKNOW */
|
||||||
|
|
||||||
*data = MAPILongValue (memCtx, format);
|
*data = MAPILongValue (memCtx, format);
|
||||||
|
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
|
@ -1517,7 +1518,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
p = 0;
|
p = 0;
|
||||||
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
|
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
|
||||||
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
|
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
|
||||||
|
|
||||||
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
|
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
|
||||||
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
|
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
|
||||||
p++;
|
p++;
|
||||||
|
@ -1525,7 +1526,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
|
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
|
||||||
recipient->data[p] = MAPILongValue (msgData, 0);
|
recipient->data[p] = MAPILongValue (msgData, 0);
|
||||||
p++;
|
p++;
|
||||||
|
|
||||||
// PR_7BIT_DISPLAY_NAME_UNICODE
|
// PR_7BIT_DISPLAY_NAME_UNICODE
|
||||||
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
||||||
p++;
|
p++;
|
||||||
|
@ -1533,7 +1534,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
// PR_SMTP_ADDRESS_UNICODE
|
// PR_SMTP_ADDRESS_UNICODE
|
||||||
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
|
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
|
||||||
p++;
|
p++;
|
||||||
|
|
||||||
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
|
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
|
||||||
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
|
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
|
||||||
p++;
|
p++;
|
||||||
|
@ -1566,12 +1567,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
withPrefix: (NSString *) keyPrefix
|
withPrefix: (NSString *) keyPrefix
|
||||||
{
|
{
|
||||||
NSArray *parts;
|
NSArray *parts;
|
||||||
NSDictionary *parameters;
|
|
||||||
NSUInteger count, max;
|
NSUInteger count, max;
|
||||||
|
|
||||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
if ([[bodyInfo filename] length] > 0)
|
||||||
objectForKey: @"parameterList"];
|
|
||||||
if ([[parameters objectForKey: @"filename"] length] > 0)
|
|
||||||
{
|
{
|
||||||
if ([keyPrefix length] == 0)
|
if ([keyPrefix length] == 0)
|
||||||
keyPrefix = @"0";
|
keyPrefix = @"0";
|
||||||
|
@ -1645,15 +1643,13 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
return attachment;
|
return attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int) setReadFlag: (uint8_t) flag
|
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||||
{
|
{
|
||||||
NSString *imapFlag = @"\\Seen";
|
|
||||||
|
|
||||||
/* TODO: notifications should probably be emitted from here */
|
/* TODO: notifications should probably be emitted from here */
|
||||||
if (flag & CLEAR_READ_FLAG)
|
if (flag & CLEAR_READ_FLAG)
|
||||||
[sogoObject removeFlags: imapFlag];
|
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
|
||||||
else
|
else
|
||||||
[sogoObject addFlags: imapFlag];
|
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
|
||||||
|
|
||||||
return MAPISTORE_SUCCESS;
|
return MAPISTORE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -1694,9 +1690,12 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) save: (TALLOC_CTX *) memCtx
|
- (void) save: (TALLOC_CTX *) memCtx
|
||||||
{
|
{
|
||||||
|
BOOL modified = NO;
|
||||||
|
BOOL seen, storedSeenFlag;
|
||||||
NSNumber *value;
|
NSNumber *value;
|
||||||
|
NSString *imapFlag = @"\\Seen";
|
||||||
|
|
||||||
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
|
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
|
||||||
if (value)
|
if (value)
|
||||||
|
@ -1706,8 +1705,35 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||||
[sogoObject addFlags: @"\\Flagged"];
|
[sogoObject addFlags: @"\\Flagged"];
|
||||||
else /* 0: unflagged, 1: follow up complete */
|
else /* 0: unflagged, 1: follow up complete */
|
||||||
[sogoObject removeFlags: @"\\Flagged"];
|
[sogoObject removeFlags: @"\\Flagged"];
|
||||||
|
|
||||||
|
modified = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Manage seen flag on save */
|
||||||
|
value = [properties objectForKey: @"read_flag_set"];
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
seen = [value boolValue];
|
||||||
|
storedSeenFlag = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"] containsObject: @"seen"];
|
||||||
|
/* We modify the flags anyway to generate a new change number */
|
||||||
|
if (seen)
|
||||||
|
{
|
||||||
|
if (storedSeenFlag)
|
||||||
|
[sogoObject removeFlags: imapFlag];
|
||||||
|
[sogoObject addFlags: imapFlag];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!storedSeenFlag)
|
||||||
|
[sogoObject addFlags: imapFlag];
|
||||||
|
[sogoObject removeFlags: imapFlag];
|
||||||
|
}
|
||||||
|
modified = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified)
|
||||||
|
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
||||||
|
|
||||||
if (mailIsSharingObject)
|
if (mailIsSharingObject)
|
||||||
[[self _sharingObject] saveWithMessage: self
|
[[self _sharingObject] saveWithMessage: self
|
||||||
andSOGoObject: sogoObject];
|
andSOGoObject: sogoObject];
|
||||||
|
|
|
@ -161,15 +161,30 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
|
||||||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||||
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
|
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
|
||||||
if (modseq)
|
if (modseq)
|
||||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
{
|
||||||
[modseq unsignedLongLongValue] + 1];
|
if (res->relop == RELOP_GT)
|
||||||
|
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||||
|
[modseq unsignedLongLongValue] + 1];
|
||||||
|
|
||||||
|
}
|
||||||
else
|
else
|
||||||
modseq = [NSNumber numberWithUnsignedLongLong: 0];
|
modseq = [NSNumber numberWithUnsignedLongLong: 0];
|
||||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
|
||||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
if (res->relop == RELOP_GT || res->relop == RELOP_GE)
|
||||||
value: modseq];
|
{
|
||||||
[*qualifier autorelease];
|
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||||
rc = MAPIRestrictionStateNeedsEval;
|
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||||
|
value: modseq];
|
||||||
|
[*qualifier autorelease];
|
||||||
|
rc = MAPIRestrictionStateNeedsEval;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Ignore other operations as IMAP only support MODSEQ >= X */
|
||||||
|
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
|
||||||
|
[self operatorFromRestrictionOperator: res->relop]];
|
||||||
|
rc = MAPIRestrictionStateAlwaysTrue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#import <NGExtensions/NGBase64Coding.h>
|
#import <NGExtensions/NGBase64Coding.h>
|
||||||
#import <NGExtensions/NGHashMap.h>
|
#import <NGExtensions/NGHashMap.h>
|
||||||
#import <NGExtensions/NSObject+Logs.h>
|
#import <NGExtensions/NSObject+Logs.h>
|
||||||
|
#import <NGExtensions/NSObject+Values.h>
|
||||||
#import <NGExtensions/NSString+Encoding.h>
|
#import <NGExtensions/NSString+Encoding.h>
|
||||||
#import <NGMime/NGMimeBodyPart.h>
|
#import <NGMime/NGMimeBodyPart.h>
|
||||||
#import <NGMime/NGMimeMultipartBody.h>
|
#import <NGMime/NGMimeMultipartBody.h>
|
||||||
|
@ -166,9 +167,9 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||||
|
|
||||||
- (BOOL) hasContentId
|
- (BOOL) hasContentId
|
||||||
{
|
{
|
||||||
return ([properties
|
NSString *contentId = [properties
|
||||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
|
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
||||||
!= nil);
|
return contentId && [contentId length] > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NGMimeBodyPart *) asMIMEBodyPart
|
- (NGMimeBodyPart *) asMIMEBodyPart
|
||||||
|
@ -232,7 +233,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||||
[map addObject: contentDisposition forKey: @"content-disposition"];
|
[map addObject: contentDisposition forKey: @"content-disposition"];
|
||||||
contentId = [properties
|
contentId = [properties
|
||||||
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
||||||
if (contentId)
|
if (contentId && [contentId length] > 0)
|
||||||
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
|
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
|
||||||
forKey: @"content-id"];
|
forKey: @"content-id"];
|
||||||
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
|
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
|
||||||
|
@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||||
version = [properties objectForKey: @"version"];
|
version = [properties objectForKey: @"version"];
|
||||||
|
|
||||||
return (version
|
return (version
|
||||||
? exchange_globcnt ([version unsignedLongLongValue])
|
? [version unsignedLongLongValue]
|
||||||
: ULLONG_MAX);
|
: ULLONG_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1110,7 +1111,8 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||||
|
|
||||||
- (void) save: (TALLOC_CTX *) memCtx
|
- (void) save: (TALLOC_CTX *) memCtx
|
||||||
{
|
{
|
||||||
NSString *folderName, *flag, *newIdString, *messageKey;
|
BOOL updatedMetadata;
|
||||||
|
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
|
||||||
NSData *changeKey, *messageData;
|
NSData *changeKey, *messageData;
|
||||||
NGImap4Connection *connection;
|
NGImap4Connection *connection;
|
||||||
NGImap4Client *client;
|
NGImap4Client *client;
|
||||||
|
@ -1146,21 +1148,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||||
[sogoObject setNameInContainer: messageKey];
|
[sogoObject setNameInContainer: messageKey];
|
||||||
[mapping registerURL: [self url] withID: mid];
|
[mapping registerURL: [self url] withID: mid];
|
||||||
|
|
||||||
/* synchronise the cache and update the change key with the one provided
|
/* synchronise the cache and update the predecessor change list
|
||||||
by the client. Before doing this, lets issue a unselect/select combo
|
with the change key provided by the client. Before doing
|
||||||
because of timing issues with Dovecot in obtaining the latest modseq.
|
this, lets issue a unselect/select combo because of timing
|
||||||
Sometimes, Dovecot doesn't return the newly appended UID if we do
|
issues with Dovecot in obtaining the latest modseq.
|
||||||
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
|
Sometimes, Dovecot doesn't return the newly appended UID if
|
||||||
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
|
we do a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)"
|
||||||
|
command (where XYZ is the HIGHESTMODSEQ+1) immediately after
|
||||||
|
IMAP APPEND */
|
||||||
[client unselect];
|
[client unselect];
|
||||||
[client select: folderName];
|
[client select: folderName];
|
||||||
|
|
||||||
[(MAPIStoreMailFolder *) container synchroniseCache];
|
[(MAPIStoreMailFolder *) container synchroniseCache];
|
||||||
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||||
if (changeKey)
|
if (changeKey)
|
||||||
[(MAPIStoreMailFolder *) container
|
{
|
||||||
setChangeKey: changeKey
|
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
|
||||||
forMessageWithKey: messageKey];
|
forMessageWithKey: messageKey];
|
||||||
|
if (!updatedMetadata)
|
||||||
|
[self warnWithFormat: @"Predecessor change list not updated with client data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update version property (PR_CHANGE_KEY indeed) as it is
|
||||||
|
requested once it is saved */
|
||||||
|
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
|
||||||
|
if (changeNumber)
|
||||||
|
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
|
||||||
|
forKey: @"version"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
withAID: (uint32_t) aid;
|
withAID: (uint32_t) aid;
|
||||||
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
|
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
|
||||||
andRowCount: (uint32_t *) countPtr;
|
andRowCount: (uint32_t *) countPtr;
|
||||||
- (int) setReadFlag: (uint8_t) flag;
|
- (enum mapistore_error) setReadFlag: (uint8_t) flag;
|
||||||
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
|
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
|
||||||
|
|
||||||
- (NSArray *) activeContainerMessageTables;
|
- (NSArray *) activeContainerMessageTables;
|
||||||
|
|
|
@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
|
||||||
}
|
}
|
||||||
|
|
||||||
[self save: memCtx];
|
[self save: memCtx];
|
||||||
/* We make sure that any change-related properties are removes from the
|
/* We make sure that any change-related properties are removed from the
|
||||||
properties dictionary, to make sure that related methods will be
|
properties dictionary, to make sure that related methods will be
|
||||||
invoked the next time they are requested. */
|
invoked the next time they are requested. */
|
||||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
|
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
|
||||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
|
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
|
||||||
|
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||||
|
|
||||||
if ([container isKindOfClass: MAPIStoreFolderK])
|
if ([container isKindOfClass: MAPIStoreFolderK])
|
||||||
{
|
{
|
||||||
|
@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
|
||||||
return [self getNo: data inMemCtx: memCtx];;
|
return [self getNo: data inMemCtx: memCtx];;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int) setReadFlag: (uint8_t) flag
|
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||||
{
|
{
|
||||||
// [self subclassResponsibility: _cmd];
|
// [self subclassResponsibility: _cmd];
|
||||||
|
|
||||||
|
|
|
@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||||
uint32_t mid_count,
|
uint32_t mid_count,
|
||||||
uint64_t *src_mids, uint64_t *t_mids,
|
uint64_t *src_mids, uint64_t *t_mids,
|
||||||
struct Binary_r **target_change_keys,
|
struct Binary_r **target_change_keys,
|
||||||
|
struct Binary_r **target_predecessor_change_lists,
|
||||||
uint8_t want_copy)
|
uint8_t want_copy)
|
||||||
{
|
{
|
||||||
MAPIStoreFolder *sourceFolder, *targetFolder;
|
MAPIStoreFolder *sourceFolder, *targetFolder;
|
||||||
|
@ -698,6 +699,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||||
fromFolder: sourceFolder
|
fromFolder: sourceFolder
|
||||||
withMIDs: t_mids
|
withMIDs: t_mids
|
||||||
andChangeKeys: target_change_keys
|
andChangeKeys: target_change_keys
|
||||||
|
andPredecessorChangeLists: target_predecessor_change_lists
|
||||||
wantCopy: want_copy
|
wantCopy: want_copy
|
||||||
inMemCtx: mem_ctx];
|
inMemCtx: mem_ctx];
|
||||||
TRYCATCH_END(pool)
|
TRYCATCH_END(pool)
|
||||||
|
@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
|
||||||
struct MAPIStoreTallocWrapper *wrapper;
|
struct MAPIStoreTallocWrapper *wrapper;
|
||||||
NSAutoreleasePool *pool;
|
NSAutoreleasePool *pool;
|
||||||
MAPIStoreMessage *message;
|
MAPIStoreMessage *message;
|
||||||
int rc;
|
enum mapistore_error rc;
|
||||||
|
|
||||||
if (message_object)
|
if (message_object)
|
||||||
{
|
{
|
||||||
|
|
|
@ -531,7 +531,7 @@
|
||||||
}
|
}
|
||||||
[vToDo setTimeStampAsDate: now];
|
[vToDo setTimeStampAsDate: now];
|
||||||
|
|
||||||
[sogoObject saveComponent: vCalendar];
|
[sogoObject saveCalendar: vCalendar];
|
||||||
|
|
||||||
[self updateVersions];
|
[self updateVersions];
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
|
|
||||||
+ (id) dataWithXID: (const struct XID *) xid;
|
+ (id) dataWithXID: (const struct XID *) xid;
|
||||||
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
|
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
|
||||||
|
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||||
|
with: (uint32_t *) length;
|
||||||
|
|
||||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||||
andCnt: (NSData *) globCnt;
|
andCnt: (NSData *) globCnt;
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#import <NGExtensions/NSObject+Logs.h>
|
#import <NGExtensions/NSObject+Logs.h>
|
||||||
|
|
||||||
|
#import "MAPIStoreTypes.h"
|
||||||
#import "NSObject+MAPIStore.h"
|
#import "NSObject+MAPIStore.h"
|
||||||
#import "NSString+MAPIStore.h"
|
#import "NSString+MAPIStore.h"
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
|
|
||||||
#undef DEBUG
|
#undef DEBUG
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <libmapi/libmapi.h>
|
||||||
#include <talloc.h>
|
#include <talloc.h>
|
||||||
#include <util/time.h>
|
#include <util/time.h>
|
||||||
#include <gen_ndr/exchange.h>
|
#include <gen_ndr/exchange.h>
|
||||||
|
@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||||
NSMutableData *xidData;
|
NSMutableData *xidData;
|
||||||
struct FlatUID_r flatUID;
|
struct FlatUID_r flatUID;
|
||||||
|
|
||||||
_fillFlatUIDWithGUID (&flatUID, &xid->GUID);
|
_fillFlatUIDWithGUID (&flatUID, &xid->NameSpaceGuid);
|
||||||
|
|
||||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->Size];
|
xidData = [NSMutableData dataWithCapacity: 16 + xid->LocalId.length];
|
||||||
[xidData appendBytes: flatUID.ab length: 16];
|
[xidData appendBytes: flatUID.ab length: 16];
|
||||||
[xidData appendBytes: xid->Data length: xid->Size];
|
[xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||||
|
|
||||||
return xidData;
|
return xidData;
|
||||||
}
|
}
|
||||||
|
@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||||
{
|
{
|
||||||
xid = talloc_zero (memCtx, struct XID);
|
xid = talloc_zero (memCtx, struct XID);
|
||||||
|
|
||||||
[self _extractGUID: &xid->GUID];
|
[self _extractGUID: &xid->NameSpaceGuid];
|
||||||
|
|
||||||
xid->Size = max - 16;
|
xid->LocalId.length = max - 16;
|
||||||
|
|
||||||
bytes = (uint8_t *) [self bytes];
|
bytes = (uint8_t *) [self bytes];
|
||||||
xid->Data = talloc_memdup (xid, (bytes+16), xid->Size);
|
xid->LocalId.data = talloc_memdup (xid, (bytes+16), xid->LocalId.length);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||||
return xid;
|
return xid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||||
|
with: (uint32_t *) length
|
||||||
|
{
|
||||||
|
struct Binary_r bin;
|
||||||
|
struct SizedXid *sizedXIDArray;
|
||||||
|
|
||||||
|
bin.cb = [self length];
|
||||||
|
bin.lpb = (uint8_t *)[self bytes];
|
||||||
|
|
||||||
|
sizedXIDArray = get_SizedXidArray(memCtx, &bin, length);
|
||||||
|
if (!sizedXIDArray)
|
||||||
|
{
|
||||||
|
NSLog (@"Impossible to parse SizedXID array");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizedXIDArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSComparisonResult) compare: (NSData *) otherGlobCnt
|
||||||
|
{
|
||||||
|
uint64_t globCnt = 0, oGlobCnt = 0;
|
||||||
|
|
||||||
|
if ([self length] > 0)
|
||||||
|
globCnt = *(uint64_t *) [self bytes];
|
||||||
|
|
||||||
|
if ([otherGlobCnt length] > 0)
|
||||||
|
oGlobCnt = *(uint64_t *) [otherGlobCnt bytes];
|
||||||
|
|
||||||
|
return MAPICNCompare (globCnt, oGlobCnt, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||||
andCnt: (NSData *) globCnt;
|
andCnt: (NSData *) globCnt;
|
||||||
{
|
{
|
||||||
|
|
|
@ -500,7 +500,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60;
|
||||||
ms = [NSMutableString stringWithCapacity: 256];
|
ms = [NSMutableString stringWithCapacity: 256];
|
||||||
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
|
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
|
||||||
|
|
||||||
[ms appendFormat: @" #adaptors=%d", [urlToAdaptor count]];
|
[ms appendFormat: @" #adaptors=%d", (int)[urlToAdaptor count]];
|
||||||
|
|
||||||
[ms appendString: @">"];
|
[ms appendString: @">"];
|
||||||
return ms;
|
return ms;
|
||||||
|
|
|
@ -414,7 +414,7 @@
|
||||||
if (group)
|
if (group)
|
||||||
[str appendFormat: @"%@ (group: %@)\n", tag, group];
|
[str appendFormat: @"%@ (group: %@)\n", tag, group];
|
||||||
else
|
else
|
||||||
[str appendFormat: @"%@\n", tag, group];
|
[str appendFormat: @"%@\n", tag];
|
||||||
|
|
||||||
[str appendString: [self versitString]];
|
[str appendString: [self versitString]];
|
||||||
|
|
||||||
|
|
|
@ -409,7 +409,7 @@ static NGCardsSaxHandler *sax = nil;
|
||||||
max = [children count];
|
max = [children count];
|
||||||
if (max > 0)
|
if (max > 0)
|
||||||
{
|
{
|
||||||
[str appendFormat: @"\n %d children: {\n", [children count]];
|
[str appendFormat: @"\n %d children: {\n", (int)[children count]];
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
[str appendFormat: @" %@\n",
|
[str appendFormat: @" %@\n",
|
||||||
[[children objectAtIndex: count] description]];
|
[[children objectAtIndex: count] description]];
|
||||||
|
|
|
@ -32,16 +32,16 @@
|
||||||
- (NSString *) iCalFormattedDateTimeString
|
- (NSString *) iCalFormattedDateTimeString
|
||||||
{
|
{
|
||||||
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
|
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
|
||||||
[self yearOfCommonEra], [self monthOfYear],
|
(int)[self yearOfCommonEra], (int)[self monthOfYear],
|
||||||
[self dayOfMonth], [self hourOfDay],
|
(int)[self dayOfMonth], (int)[self hourOfDay],
|
||||||
[self minuteOfHour], [self secondOfMinute]];
|
(int)[self minuteOfHour], (int)[self secondOfMinute]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *) iCalFormattedDateString
|
- (NSString *) iCalFormattedDateString
|
||||||
{
|
{
|
||||||
return [NSString stringWithFormat: @"%.4d%.2d%.2d",
|
return [NSString stringWithFormat: @"%.4d%.2d%.2d",
|
||||||
[self yearOfCommonEra], [self monthOfYear],
|
(int)[self yearOfCommonEra], (int)[self monthOfYear],
|
||||||
[self dayOfMonth]];
|
(int)[self dayOfMonth]];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -158,16 +158,31 @@
|
||||||
NSMutableString *string;
|
NSMutableString *string;
|
||||||
unsigned int len, i;
|
unsigned int len, i;
|
||||||
unichar c;
|
unichar c;
|
||||||
|
BOOL isQuoted;
|
||||||
|
|
||||||
len = [self length];
|
len = [self length];
|
||||||
string = [NSMutableString stringWithCapacity: len * 1.5];
|
string = [NSMutableString stringWithCapacity: len * 1.5];
|
||||||
|
isQuoted = NO;
|
||||||
|
|
||||||
for (i = 0; i < len; i++)
|
for (i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
c = [self characterAtIndex: i];
|
c = [self characterAtIndex: i];
|
||||||
|
|
||||||
|
if (isQuoted)
|
||||||
|
{
|
||||||
|
if (c == '"')
|
||||||
|
isQuoted = NO;
|
||||||
|
|
||||||
|
[string appendFormat: @"%C", c];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
|
case '"':
|
||||||
|
isQuoted = YES;
|
||||||
|
[string appendFormat: @"%C", c];
|
||||||
|
break;
|
||||||
case '\\':
|
case '\\':
|
||||||
[string appendString: @"\\\\"];
|
[string appendString: @"\\\\"];
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#import "NSCalendarDate+NGCards.h"
|
#import "NSCalendarDate+NGCards.h"
|
||||||
|
|
||||||
#import "iCalAlarm.h"
|
#import "iCalAlarm.h"
|
||||||
|
#import "iCalCalendar.h"
|
||||||
#import "iCalDateTime.h"
|
#import "iCalDateTime.h"
|
||||||
#import "iCalEntityObject.h"
|
#import "iCalEntityObject.h"
|
||||||
#import "iCalEvent.h"
|
#import "iCalEvent.h"
|
||||||
|
@ -268,9 +269,12 @@
|
||||||
- (void) setRecurrenceId: (NSCalendarDate *) newRecId
|
- (void) setRecurrenceId: (NSCalendarDate *) newRecId
|
||||||
{
|
{
|
||||||
iCalDateTime* recurrenceId;
|
iCalDateTime* recurrenceId;
|
||||||
|
BOOL isMasterAllDay;
|
||||||
|
|
||||||
|
isMasterAllDay = [[[[self parent] events] objectAtIndex: 0] isAllDay];
|
||||||
|
|
||||||
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
|
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
|
||||||
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay])
|
if ([self isKindOfClass: [iCalEvent class]] && isMasterAllDay)
|
||||||
[recurrenceId setDate: newRecId];
|
[recurrenceId setDate: newRecId];
|
||||||
else
|
else
|
||||||
[recurrenceId setDateTime: newRecId];
|
[recurrenceId setDateTime: newRecId];
|
||||||
|
|
|
@ -348,7 +348,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
|
||||||
if ([byDayMask occursOnDay: currentWeekDay])
|
if ([byDayMask occursOnDay: currentWeekDay])
|
||||||
{
|
{
|
||||||
if ([bySetPos containsObject:
|
if ([bySetPos containsObject:
|
||||||
[NSString stringWithFormat: @"%d", currentPos]])
|
[NSString stringWithFormat: @"%d", (int)currentPos]])
|
||||||
monthDays[monthDay+1] = YES;
|
monthDays[monthDay+1] = YES;
|
||||||
currentPos++;
|
currentPos++;
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
|
||||||
if ([byDayMask occursOnDay: currentWeekDay])
|
if ([byDayMask occursOnDay: currentWeekDay])
|
||||||
{
|
{
|
||||||
if ([bySetPos containsObject:
|
if ([bySetPos containsObject:
|
||||||
[NSString stringWithFormat: @"%d", currentPos]])
|
[NSString stringWithFormat: @"%d", (int)currentPos]])
|
||||||
monthDays[monthDay] = YES;
|
monthDays[monthDay] = YES;
|
||||||
currentPos--;
|
currentPos--;
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,7 +267,7 @@
|
||||||
- (BOOL)isEqual:(id)_other {
|
- (BOOL)isEqual:(id)_other {
|
||||||
if(_other == nil)
|
if(_other == nil)
|
||||||
return NO;
|
return NO;
|
||||||
if([_other class] != self->isa)
|
if([_other class] != object_getClass(self))
|
||||||
return NO;
|
return NO;
|
||||||
if([_other hash] != [self hash])
|
if([_other hash] != [self hash])
|
||||||
return NO;
|
return NO;
|
||||||
|
|
|
@ -309,10 +309,10 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
|
||||||
else if ([frequency isEqualToString:@"SECONDLY"])
|
else if ([frequency isEqualToString:@"SECONDLY"])
|
||||||
freq = iCalRecurrenceFrequenceSecondly;
|
freq = iCalRecurrenceFrequenceSecondly;
|
||||||
else
|
else
|
||||||
freq = NSNotFound;
|
freq = (iCalRecurrenceFrequency) NSNotFound;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
freq = NSNotFound;
|
freq = (iCalRecurrenceFrequency) NSNotFound;
|
||||||
|
|
||||||
return freq;
|
return freq;
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,8 +161,11 @@
|
||||||
minute: [tzStart minuteOfHour] second: 0
|
minute: [tzStart minuteOfHour] second: 0
|
||||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
|
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
|
||||||
|
|
||||||
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1)
|
tmpDate = [tmpDate addYear: 0
|
||||||
day: 0 hour: 0 minute: 0
|
month: ((pos > 0) ? 0 : 1)
|
||||||
|
day: 0
|
||||||
|
hour: 0
|
||||||
|
minute: 0
|
||||||
second: 0];
|
second: 0];
|
||||||
|
|
||||||
/* If the day of the time change is "-XSU", we need to determine whether the
|
/* If the day of the time change is "-XSU", we need to determine whether the
|
||||||
|
@ -197,12 +200,41 @@
|
||||||
END:STANDARD
|
END:STANDARD
|
||||||
END:VTIMEZONE
|
END:VTIMEZONE
|
||||||
|
|
||||||
The time changes occure on a Sunday, but in March, the 1st is a Sunday itself and in November
|
The time changes occur on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||||
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
|
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
|
||||||
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
|
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
|
||||||
effectively shift the time change by a whole week.
|
effectively shift the time change by a whole week.
|
||||||
|
|
||||||
|
In Europe/Berlin, we have a different use-case for November. In 2015, November 1st is a Sunday.
|
||||||
|
The time change in November must occur on October 25th but since tmpDate will be November 1st,
|
||||||
|
so a Sunday, dateDayOfWeek will be 0 and dayOfWeek will also be 0 we would decrement tmpDate by 14 days,
|
||||||
|
which is incorrect because it would shift the timezone change one week earlier. We take care about this
|
||||||
|
one with check if pos is greater or equal than 0 and if so, we don't decrement it.
|
||||||
|
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Inverse inc.//NONSGML Olson 2014g//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
X-LIC-LOCATION:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
END:VCALENDAR
|
||||||
*/
|
*/
|
||||||
if (dayOfWeek == dateDayOfWeek)
|
if (dayOfWeek == dateDayOfWeek && pos >= 0)
|
||||||
pos--;
|
pos--;
|
||||||
|
|
||||||
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
|
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
- (NSDictionary *) asDictionary
|
- (NSDictionary *) asDictionary
|
||||||
{
|
{
|
||||||
NSDictionary *data;
|
NSDictionary *data;
|
||||||
NSString *duration, *relation, *reference, *quantity, *unit;
|
NSString *duration, *relation, *reference, *quantity=@"", *unit=@"";
|
||||||
NSUInteger i;
|
NSUInteger i;
|
||||||
unichar c;
|
unichar c;
|
||||||
|
|
||||||
|
|
|
@ -1045,7 +1045,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
|
||||||
if (debugOn)
|
if (debugOn)
|
||||||
{
|
{
|
||||||
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...",
|
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...",
|
||||||
__PRETTY_FUNCTION__, _data, [_data length]);
|
__PRETTY_FUNCTION__, _data, (int)[_data length]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((len = [_data length]) == 0)
|
if ((len = [_data length]) == 0)
|
||||||
|
@ -1164,7 +1164,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
|
||||||
if (debugOn)
|
if (debugOn)
|
||||||
{
|
{
|
||||||
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
|
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
|
||||||
__PRETTY_FUNCTION__, _source, [_source length]);
|
__PRETTY_FUNCTION__, _source, (int)[_source length]);
|
||||||
}
|
}
|
||||||
if (!_sysId) _sysId = @"<string>";
|
if (!_sysId) _sysId = @"<string>";
|
||||||
[self _parseString: _source];
|
[self _parseString: _source];
|
||||||
|
|
|
@ -1,309 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import getopt
|
|
||||||
import imaplib
|
|
||||||
import ldb
|
|
||||||
import plistlib
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from samba.param import LoadParm
|
|
||||||
|
|
||||||
imaphost = '127.0.0.1'
|
|
||||||
imapport = 143
|
|
||||||
|
|
||||||
samba_lp = LoadParm()
|
|
||||||
sambaprivate = samba_lp.get("private dir")
|
|
||||||
mapistorefolder = samba_lp.private_path("mapistore")
|
|
||||||
sogoSysDefaultsFile = "/etc/sogo/sogo.conf"
|
|
||||||
sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaults")
|
|
||||||
|
|
||||||
# - takes a username and optionally its password
|
|
||||||
# - removes the entry in samba's ldap tree via ldbedit (NOTYET)
|
|
||||||
# - remove the user's directory under mapistore/ and mapistore/SOGo
|
|
||||||
# - cleanup Junk Folders and Sync Issues imap folders
|
|
||||||
# - Delete the sogo_cache_folder_ table for the username.
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print """
|
|
||||||
%s [-i imaphost] ] [-p imapport] [-s sambaprivate] username [password]
|
|
||||||
-i imaphost IMAP host to connect to [%s]
|
|
||||||
-p imappost IMAP port to use [%d]
|
|
||||||
-s sambaprivate samba private directory [%s]
|
|
||||||
""" % (os.path.basename(sys.argv[0]), imaphost, imapport, sambaprivate)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
global sambaprivate
|
|
||||||
global mapistorefolder
|
|
||||||
global imaphost
|
|
||||||
global imapport
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "i:p:s:")
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
print str(err)
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
for o, a in opts:
|
|
||||||
if o == "-i":
|
|
||||||
imaphost = a
|
|
||||||
elif o == "-p":
|
|
||||||
imapport = a
|
|
||||||
elif o == "-s":
|
|
||||||
sambaprivate = a
|
|
||||||
mapistorefolder = "%s/mapistore" % (sambaprivate)
|
|
||||||
else:
|
|
||||||
assert False, "unhandled option"
|
|
||||||
|
|
||||||
argslen = len(args)
|
|
||||||
if (argslen == 2):
|
|
||||||
username = args[0]
|
|
||||||
userpass = args[1]
|
|
||||||
elif (argslen == 1):
|
|
||||||
username = args[0]
|
|
||||||
userpass = username
|
|
||||||
print "Using username as password"
|
|
||||||
else:
|
|
||||||
usage()
|
|
||||||
print "Specify a user (and optionally the password)"
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
# cleanup starts here
|
|
||||||
try:
|
|
||||||
imapCleanup(imaphost, imapport, username, userpass)
|
|
||||||
except Exception as e:
|
|
||||||
print "Error during imapCleanup, continuing: %s" % str(e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
mapistoreCleanup(mapistorefolder, username)
|
|
||||||
except (shutil.Error, OSError) as e:
|
|
||||||
print "Error during mapistoreCleanup, continuing: %s" % str(e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
ldbCleanup(sambaprivate, username)
|
|
||||||
except ldb.LdbError as e:
|
|
||||||
print "Error during ldbCleanup, continuing: %s" % str(e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
sqlCleanup(username)
|
|
||||||
except Exception as e:
|
|
||||||
print "Error during sqlCleanup, continuing: %s" % str(e)
|
|
||||||
|
|
||||||
def getsep(client):
|
|
||||||
seq = None
|
|
||||||
(code, data) = client.list("", "")
|
|
||||||
if code == "OK" and data is not None:
|
|
||||||
# yes this is ugly but it works on cyrus and dovecot.
|
|
||||||
# (\\Noinferiors \\HasNoChildren) "/" INBOX
|
|
||||||
m = re.search(".*\s+[\"\']?(.)[\"\']?\s+[\"\']?.*[\"\']?$", data[0])
|
|
||||||
sep = m.group(1)
|
|
||||||
return sep
|
|
||||||
|
|
||||||
def extractmb(si):
|
|
||||||
inparen = False
|
|
||||||
inquote = False
|
|
||||||
part = []
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
for char in si:
|
|
||||||
if inparen:
|
|
||||||
if char == ")":
|
|
||||||
inparen = False
|
|
||||||
parts.append("".join(part))
|
|
||||||
else:
|
|
||||||
part.append(char)
|
|
||||||
elif inquote:
|
|
||||||
if char == "\"":
|
|
||||||
inquote = False
|
|
||||||
parts.append("".join(part))
|
|
||||||
else:
|
|
||||||
part.append(char)
|
|
||||||
else:
|
|
||||||
if char == "(":
|
|
||||||
inparen = True
|
|
||||||
elif char == "\"":
|
|
||||||
inquote = True
|
|
||||||
elif char == " ":
|
|
||||||
part = []
|
|
||||||
else:
|
|
||||||
part.append(char)
|
|
||||||
|
|
||||||
return parts[-1]
|
|
||||||
|
|
||||||
def cleanupmb(mb, sep, client):
|
|
||||||
(code, data) = client.list("%s%s" % (mb, sep), "%")
|
|
||||||
if code == "OK":
|
|
||||||
for si in data:
|
|
||||||
if si is not None:
|
|
||||||
submb = extractmb(si)
|
|
||||||
cleanupmb(submb, sep, client)
|
|
||||||
else:
|
|
||||||
raise Exception, "Failure while cleaning up '%s'" % mb
|
|
||||||
client.unsubscribe(mb)
|
|
||||||
(code, data) = client.delete(mb)
|
|
||||||
if code == "OK":
|
|
||||||
print "mailbox '%s' deleted" % mb
|
|
||||||
else:
|
|
||||||
print "mailbox '%s' coult NOT be deleted (code = '%s')" % (mb, code)
|
|
||||||
|
|
||||||
def imapCleanup(imaphost, imapport, username, userpass):
|
|
||||||
print "Starting IMAP cleanup"
|
|
||||||
client = imaplib.IMAP4(imaphost, imapport)
|
|
||||||
(code, data) = client.login(username, userpass)
|
|
||||||
if code != "OK":
|
|
||||||
raise Exception, "Login failure"
|
|
||||||
|
|
||||||
print "Logged in as '%s'" % username
|
|
||||||
|
|
||||||
sep = getsep(client)
|
|
||||||
if not sep:
|
|
||||||
client.logout()
|
|
||||||
return
|
|
||||||
|
|
||||||
for foldername in ("Sync Issues", "Junk E-mail",
|
|
||||||
"INBOX%sSync Issues" % sep, "INBOX%sJunk E-mail" % sep,
|
|
||||||
"Probl&AOg-mes de synchronisation"):
|
|
||||||
(code, data) = client.list(foldername, "%")
|
|
||||||
if code == "OK":
|
|
||||||
for si in data:
|
|
||||||
if si is not None:
|
|
||||||
mb = extractmb(si)
|
|
||||||
cleanupmb(mb, sep, client)
|
|
||||||
client.logout()
|
|
||||||
|
|
||||||
def mapistoreCleanup(mapistorefolder, username):
|
|
||||||
print "Starting MAPIstore cleanup"
|
|
||||||
|
|
||||||
# delete the user's folder under the mapistore and under mapistore/SOGo
|
|
||||||
mapistoreUserDir = "%s/%s" % (mapistorefolder, username)
|
|
||||||
for dirpath, dirnames, filenames in os.walk(mapistoreUserDir):
|
|
||||||
for f in filenames:
|
|
||||||
if f != "password":
|
|
||||||
os.unlink("%s/%s" % (dirpath,f))
|
|
||||||
break #one level only
|
|
||||||
|
|
||||||
shutil.rmtree("%s/SOGo/%s" % (mapistorefolder, username), ignore_errors=True)
|
|
||||||
|
|
||||||
def ldbCleanup(sambaprivate, username):
|
|
||||||
conn = ldb.Ldb("%s/openchange.ldb" % (sambaprivate))
|
|
||||||
# find the DN of the user
|
|
||||||
entries = conn.search(None, expression="(cn=%s)" % (username), scope=ldb.SCOPE_SUBTREE)
|
|
||||||
if not entries:
|
|
||||||
print "cn = %s not found in openchange.ldb" %(username)
|
|
||||||
return
|
|
||||||
|
|
||||||
for entry in entries:
|
|
||||||
# search again, but with the user's DN as a base
|
|
||||||
subentries = conn.search(entry.dn.extended_str(), expression="(distinguishedName=*)", scope=ldb.SCOPE_SUBTREE)
|
|
||||||
for subentry in subentries:
|
|
||||||
print "Deleting %s" % (subentry.dn)
|
|
||||||
conn.delete(subentry.dn)
|
|
||||||
|
|
||||||
def mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
|
|
||||||
try:
|
|
||||||
import MySQLdb
|
|
||||||
except ImportError:
|
|
||||||
msg ="""The python 'MySQLdb' module is not available
|
|
||||||
On Debian based distro, install it using 'apt-get install python-mysqlbd'
|
|
||||||
On RHEL, install it using 'yum install MySQL-python'"""
|
|
||||||
raise Exception(msg)
|
|
||||||
|
|
||||||
conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
|
|
||||||
c=conn.cursor()
|
|
||||||
tablename="sogo_cache_folder_%s" % (username)
|
|
||||||
c.execute("TRUNCATE TABLE %s" % tablename)
|
|
||||||
print "Table %s emptied" % tablename
|
|
||||||
|
|
||||||
|
|
||||||
def postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
|
|
||||||
try:
|
|
||||||
import pg
|
|
||||||
except ImportError:
|
|
||||||
msg ="""The python 'pg' module is not available
|
|
||||||
On Debian based distro, install it using 'apt-get install python-pygresql'
|
|
||||||
On RHEL, install it using 'yum install python-pgsql'"""
|
|
||||||
raise Exception(msg)
|
|
||||||
|
|
||||||
conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname)
|
|
||||||
tablename = "sogo_cache_folder_%s" % username
|
|
||||||
conn.query("DELETE FROM %s" % tablename)
|
|
||||||
print "Table '%s' emptied" % tablename
|
|
||||||
|
|
||||||
def getOCSFolderInfoURL():
|
|
||||||
global sogoSysDefaultsFile, sogoUserDefaultsFile
|
|
||||||
|
|
||||||
OCSFolderInfoURL = ""
|
|
||||||
|
|
||||||
# read defaults from defaults files
|
|
||||||
# order is important, user defaults must have precedence
|
|
||||||
for f in [sogoSysDefaultsFile, sogoUserDefaultsFile]:
|
|
||||||
if os.path.exists(f):
|
|
||||||
# FIXME: this is ugly, we should have a python plist parser
|
|
||||||
# plistlib only supports XML plists.
|
|
||||||
# the following magic replaces this shell pipeline:
|
|
||||||
# sogo-tool dump-defaults -f %s | awk -F\\" '/ OCSFolderInfoURL =/ {print $2}'
|
|
||||||
p1 = subprocess.Popen(["sogo-tool", "dump-defaults", "-f", f], stdout=subprocess.PIPE)
|
|
||||||
p2 = subprocess.Popen(["awk", "-F\"", "/ OCSFolderInfoURL =/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
|
|
||||||
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
|
|
||||||
tmp = p2.communicate()[0]
|
|
||||||
|
|
||||||
if tmp: OCSFolderInfoURL = tmp
|
|
||||||
|
|
||||||
return OCSFolderInfoURL
|
|
||||||
|
|
||||||
def asCSSIdentifier(inputString):
|
|
||||||
cssEscapingCharMap = {"_" : "_U_",
|
|
||||||
"." : "_D_",
|
|
||||||
"#" : "_H_",
|
|
||||||
"@" : "_A_",
|
|
||||||
"*" : "_S_",
|
|
||||||
":" : "_C_",
|
|
||||||
"," : "_CO_",
|
|
||||||
" " : "_SP_",
|
|
||||||
"'" : "_SQ_",
|
|
||||||
"&" : "_AM_",
|
|
||||||
"+" : "_P_"}
|
|
||||||
|
|
||||||
newChars = []
|
|
||||||
|
|
||||||
for c in inputString:
|
|
||||||
if c in cssEscapingCharMap:
|
|
||||||
newChars.append(cssEscapingCharMap[c])
|
|
||||||
else:
|
|
||||||
newChars.append(c)
|
|
||||||
|
|
||||||
return "".join(newChars)
|
|
||||||
|
|
||||||
def sqlCleanup(username):
|
|
||||||
print "Starting SQL cleanup"
|
|
||||||
OCSFolderInfoURL = getOCSFolderInfoURL()
|
|
||||||
if OCSFolderInfoURL is None:
|
|
||||||
raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username))
|
|
||||||
|
|
||||||
# postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info
|
|
||||||
m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL)
|
|
||||||
|
|
||||||
if not m:
|
|
||||||
raise Exception("Unable to parse OCSFolderInfoURL: %s" % OCSFolderInfoURL)
|
|
||||||
|
|
||||||
proto = m.group(1)
|
|
||||||
dbuser = m.group(2)
|
|
||||||
dbpass = m.group(3)
|
|
||||||
dbhost = m.group(4)
|
|
||||||
dbport = m.group(5)
|
|
||||||
dbname = m.group(6)
|
|
||||||
# 7 is folderinfo table
|
|
||||||
|
|
||||||
encodedUserName = asCSSIdentifier(username)
|
|
||||||
|
|
||||||
if (proto == "postgresql"):
|
|
||||||
postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
|
|
||||||
elif (proto == "mysql"):
|
|
||||||
mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
|
|
||||||
else:
|
|
||||||
raise Exception("Unknown sql proto: %s" % (proto))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -344,7 +344,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
|
||||||
NSMutableString *s;
|
NSMutableString *s;
|
||||||
|
|
||||||
s = [NSMutableString stringWithCapacity: 64];
|
s = [NSMutableString stringWithCapacity: 64];
|
||||||
[s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
|
[s appendFormat:@"<0x%08X[%@]:", (unsigned int)self, NSStringFromClass([self class])];
|
||||||
if (freeBusyViewType)
|
if (freeBusyViewType)
|
||||||
[s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType];
|
[s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType];
|
||||||
if (mergedFreeBusy)
|
if (mergedFreeBusy)
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
#import "SOGoAppointmentFolders.h"
|
#import "SOGoAppointmentFolders.h"
|
||||||
#import "SOGoFreeBusyObject.h"
|
#import "SOGoFreeBusyObject.h"
|
||||||
#import "SOGoTaskObject.h"
|
#import "SOGoTaskObject.h"
|
||||||
#import "SOGoWebAppointmentFolder.h";
|
#import "SOGoWebAppointmentFolder.h"
|
||||||
|
|
||||||
#import "SOGoAppointmentFolder.h"
|
#import "SOGoAppointmentFolder.h"
|
||||||
|
|
||||||
|
@ -1013,7 +1013,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||||
{
|
{
|
||||||
if ([dateRange containsDate: [component startDate]])
|
if ([dateRange containsDate: [component startDate]])
|
||||||
{
|
{
|
||||||
// We must pass nill to :container here in order to avoid re-entrancy issues.
|
// We must pass nil to :container here in order to avoid re-entrancy issues.
|
||||||
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
|
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
|
||||||
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
|
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
|
||||||
}
|
}
|
||||||
|
@ -1030,15 +1030,20 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||||
{
|
{
|
||||||
// The recurrence id of the exception is outside the date range;
|
// The recurrence id of the exception is outside the date range;
|
||||||
// simply add the exception to the records array.
|
// simply add the exception to the records array.
|
||||||
// We must pass nill to :container here in order to avoid re-entrancy issues.
|
// We must pass nil to :container here in order to avoid re-entrancy issues.
|
||||||
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
|
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
|
||||||
newRecordRange = [NGCalendarDateRange
|
if ([newRecord objectForKey: @"startDate"] && [newRecord objectForKey: @"endDate"]) {
|
||||||
calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"]
|
newRecordRange = [NGCalendarDateRange
|
||||||
endDate: [newRecord objectForKey: @"endDate"]];
|
calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"]
|
||||||
if ([dateRange doesIntersectWithDateRange: newRecordRange])
|
endDate: [newRecord objectForKey: @"endDate"]];
|
||||||
|
if ([dateRange doesIntersectWithDateRange: newRecordRange])
|
||||||
[ma addObject: newRecord];
|
[ma addObject: newRecord];
|
||||||
else
|
else
|
||||||
|
newRecord = nil;
|
||||||
|
} else {
|
||||||
|
[self warnWithFormat: @"Recurrence %@ without dtstart or dtend. Ignoring", recurrenceId];
|
||||||
newRecord = nil;
|
newRecord = nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newRecord)
|
if (newRecord)
|
||||||
|
@ -2337,7 +2342,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||||
request = [context request];
|
request = [context request];
|
||||||
if (!([request isIPhone] || [request isICal4]))
|
if (!([request isIPhone] || [request isICal4]))
|
||||||
{
|
{
|
||||||
gdRT = [self groupDavResourceType];
|
gdRT = (NSArray *) [self groupDavResourceType];
|
||||||
gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
|
gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
|
||||||
XMLNS_GROUPDAV, nil];
|
XMLNS_GROUPDAV, nil];
|
||||||
[colType addObject: gdVEventCol];
|
[colType addObject: gdVEventCol];
|
||||||
|
@ -2988,7 +2993,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||||
|
|
||||||
theUser = [SOGoUser userWithLogin: theUID];
|
theUser = [SOGoUser userWithLogin: theUID];
|
||||||
aParent = [theUser calendarsFolderInContext: context];
|
aParent = [theUser calendarsFolderInContext: context];
|
||||||
|
|
||||||
|
if ([aParent isKindOfClass: [NSException class]])
|
||||||
|
return nil;
|
||||||
|
|
||||||
aFolders = [aParent subFolders];
|
aFolders = [aParent subFolders];
|
||||||
e = [aFolders objectEnumerator];
|
e = [aFolders objectEnumerator];
|
||||||
while ((aFolder = [e nextObject]))
|
while ((aFolder = [e nextObject]))
|
||||||
|
@ -3152,19 +3160,29 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||||
NSMutableString *content;
|
NSMutableString *content;
|
||||||
NSString *uid;
|
NSString *uid;
|
||||||
|
|
||||||
// We first look if there's an event with the same UID in our calendar. If not,
|
// We first look if the event has any / or + in its UID. If that's the case
|
||||||
// let's reuse what is in the event, otherwise generate a new GUID and use it.
|
// we generate a new UID based on a GUID
|
||||||
uid = [event uid];
|
uid = [event uid];
|
||||||
|
|
||||||
object = [self lookupName: uid
|
if ([uid rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"+/"]].location != NSNotFound)
|
||||||
inContext: context
|
|
||||||
acquire: NO];
|
|
||||||
|
|
||||||
if (object && ![object isKindOfClass: [NSException class]])
|
|
||||||
{
|
{
|
||||||
uid = [self globallyUniqueObjectId];
|
uid = [self globallyUniqueObjectId];
|
||||||
[event setUid: uid];
|
[event setUid: uid];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We also look if there's an event with the same UID in our calendar. If not,
|
||||||
|
// let's reuse what is in the event, otherwise generate a new GUID and use it.
|
||||||
|
object = [self lookupName: uid
|
||||||
|
inContext: context
|
||||||
|
acquire: NO];
|
||||||
|
|
||||||
|
if (object && ![object isKindOfClass: [NSException class]])
|
||||||
|
{
|
||||||
|
uid = [self globallyUniqueObjectId];
|
||||||
|
[event setUid: uid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object = [SOGoAppointmentObject objectWithName: uid
|
object = [SOGoAppointmentObject objectWithName: uid
|
||||||
inContainer: self];
|
inContainer: self];
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
#import <NGCards/iCalDateTime.h>
|
#import <NGCards/iCalDateTime.h>
|
||||||
#import <NGCards/iCalTimeZone.h>
|
#import <NGCards/iCalTimeZone.h>
|
||||||
#import <NGCards/iCalTimeZonePeriod.h>
|
#import <NGCards/iCalTimeZonePeriod.h>
|
||||||
|
#import <NGCards/iCalToDo.h>
|
||||||
#import <NGCards/NSString+NGCards.h>
|
#import <NGCards/NSString+NGCards.h>
|
||||||
|
|
||||||
#import <SOGo/SOGoConstants.h>
|
#import <SOGo/SOGoConstants.h>
|
||||||
|
@ -748,11 +749,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||||
{
|
{
|
||||||
e = [events objectAtIndex: i];
|
e = [events objectAtIndex: i];
|
||||||
if ([e recurrenceId])
|
if ([e recurrenceId])
|
||||||
for (j = 0; j < [theAttendees count]; j++)
|
for (j = 0; j < [theAttendees count]; j++) {
|
||||||
if (shouldAdd)
|
if (shouldAdd) {
|
||||||
[e addToAttendees: [theAttendees objectAtIndex: j]];
|
[e addToAttendees: [theAttendees objectAtIndex: j]];
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
[e removeFromAttendees: [theAttendees objectAtIndex: j]];
|
[e removeFromAttendees: [theAttendees objectAtIndex: j]];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1038,7 +1042,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||||
if ([delegateEmail length])
|
if ([delegateEmail length])
|
||||||
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
||||||
else
|
else
|
||||||
otherDelegate = NO;
|
otherDelegate = nil;
|
||||||
|
|
||||||
/* we handle the addition/deletion of delegate users */
|
/* we handle the addition/deletion of delegate users */
|
||||||
addDelegate = NO;
|
addDelegate = NO;
|
||||||
|
@ -1076,7 +1080,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||||
if ([delegateEmail length])
|
if ([delegateEmail length])
|
||||||
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
||||||
else
|
else
|
||||||
otherDelegate = NO;
|
otherDelegate = nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (addDelegate)
|
if (addDelegate)
|
||||||
|
@ -1233,7 +1237,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||||
if ([delegateEmail length])
|
if ([delegateEmail length])
|
||||||
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
|
||||||
else
|
else
|
||||||
otherDelegate = NO;
|
otherDelegate = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||||
|
@ -1992,7 +1996,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||||
if ([container resourceNameForEventUID: eventUID])
|
if ([container resourceNameForEventUID: eventUID])
|
||||||
{
|
{
|
||||||
return [NSException exceptionWithHTTPStatus: 403
|
return [NSException exceptionWithHTTPStatus: 403
|
||||||
reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]];
|
reason: [NSString stringWithFormat: @"Event UID already in use. (%@)", eventUID]];
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -140,6 +140,14 @@
|
||||||
return aclManager;
|
return aclManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSException *) changeParticipationStatus: (NSString *) newPartStat
|
||||||
|
withDelegate: (iCalPerson *) delegate
|
||||||
|
alarm: (iCalAlarm *) alarm
|
||||||
|
{
|
||||||
|
// Required for protocol <SOGoComponentOccurence>
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (id) init
|
- (id) init
|
||||||
{
|
{
|
||||||
if ((self = [super init]))
|
if ((self = [super init]))
|
||||||
|
@ -171,7 +179,7 @@
|
||||||
|
|
||||||
- (Class *) parsingClass
|
- (Class *) parsingClass
|
||||||
{
|
{
|
||||||
return [iCalCalendar class];
|
return (Class *)[iCalCalendar class];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *) davContentType
|
- (NSString *) davContentType
|
||||||
|
|
|
@ -102,9 +102,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([reminderReference caseInsensitiveCompare: @"BEFORE"] == NSOrderedSame)
|
if ([reminderReference caseInsensitiveCompare: @"BEFORE"] == NSOrderedSame)
|
||||||
aValue = [NSString stringWithString: @"-P"];
|
aValue = (NSString *) @"-P";
|
||||||
else
|
else
|
||||||
aValue = [NSString stringWithString: @"P"];
|
aValue = (NSString *) @"P";
|
||||||
|
|
||||||
if ([reminderUnit caseInsensitiveCompare: @"MINUTES"] == NSOrderedSame ||
|
if ([reminderUnit caseInsensitiveCompare: @"MINUTES"] == NSOrderedSame ||
|
||||||
[reminderUnit caseInsensitiveCompare: @"HOURS"] == NSOrderedSame)
|
[reminderUnit caseInsensitiveCompare: @"HOURS"] == NSOrderedSame)
|
||||||
|
|
|
@ -267,7 +267,7 @@
|
||||||
|
|
||||||
- (NSTimeInterval) occurenceInterval
|
- (NSTimeInterval) occurenceInterval
|
||||||
{
|
{
|
||||||
return [[self endDate] timeIntervalSinceDate: [self startDate]];
|
return (NSTimeInterval) [[self endDate] timeIntervalSinceDate: [self startDate]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -328,30 +328,10 @@
|
||||||
*/
|
*/
|
||||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||||
{
|
{
|
||||||
BOOL isAllDay;
|
|
||||||
NSCalendarDate *eventStartDate, *eventEndDate;
|
|
||||||
NSMutableDictionary *data;
|
NSMutableDictionary *data;
|
||||||
NSTimeZone *timeZone;
|
|
||||||
SOGoUserDefaults *ud;
|
|
||||||
|
|
||||||
isAllDay = [self isAllDay];
|
|
||||||
ud = [[context activeUser] userDefaults];
|
|
||||||
timeZone = [ud timeZone];
|
|
||||||
eventStartDate = [self startDate];
|
|
||||||
eventEndDate = [self endDate];
|
|
||||||
|
|
||||||
if (!isAllDay)
|
|
||||||
{
|
|
||||||
[eventStartDate setTimeZone: timeZone];
|
|
||||||
[eventEndDate setTimeZone: timeZone];
|
|
||||||
}
|
|
||||||
|
|
||||||
data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]];
|
data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]];
|
||||||
|
|
||||||
[data setObject: [eventStartDate iso8601DateString] forKey: @"startDate"];
|
|
||||||
[data setObject: [eventEndDate iso8601DateString] forKey: @"endDate"];
|
|
||||||
|
|
||||||
[data setObject: [NSNumber numberWithBool: isAllDay] forKey: @"isAllDay"];
|
|
||||||
[data setObject: [NSNumber numberWithBool: ![self isOpaque]] forKey: @"isTransparent"];
|
[data setObject: [NSNumber numberWithBool: ![self isOpaque]] forKey: @"isTransparent"];
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -383,18 +363,10 @@
|
||||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||||
aptStartDate = [self dateFromString: o inContext: context];
|
aptStartDate = [self dateFromString: o inContext: context];
|
||||||
|
|
||||||
o = [data objectForKey: @"startTime"];
|
|
||||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
|
||||||
[self adjustDate: &aptStartDate withTimeString: o inContext: context];
|
|
||||||
|
|
||||||
o = [data objectForKey: @"endDate"];
|
o = [data objectForKey: @"endDate"];
|
||||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||||
aptEndDate = [self dateFromString: o inContext: context];
|
aptEndDate = [self dateFromString: o inContext: context];
|
||||||
|
|
||||||
o = [data objectForKey: @"endTime"];
|
|
||||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
|
||||||
[self adjustDate: &aptEndDate withTimeString: o inContext: context];
|
|
||||||
|
|
||||||
o = [data objectForKey: @"isTransparent"];
|
o = [data objectForKey: @"isTransparent"];
|
||||||
if ([o isKindOfClass: [NSNumber class]])
|
if ([o isKindOfClass: [NSNumber class]])
|
||||||
[self setTransparency: ([o boolValue]? @"TRANSPARENT" : @"OPAQUE")];
|
[self setTransparency: ([o boolValue]? @"TRANSPARENT" : @"OPAQUE")];
|
||||||
|
@ -418,6 +390,14 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
o = [data objectForKey: @"startTime"];
|
||||||
|
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||||
|
[self adjustDate: &aptStartDate withTimeString: o inContext: context];
|
||||||
|
|
||||||
|
o = [data objectForKey: @"endTime"];
|
||||||
|
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||||
|
[self adjustDate: &aptEndDate withTimeString: o inContext: context];
|
||||||
|
|
||||||
[self setStartDate: aptStartDate];
|
[self setStartDate: aptStartDate];
|
||||||
[self setEndDate: aptEndDate];
|
[self setEndDate: aptEndDate];
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||||
|
|
||||||
#import "iCalRepeatableEntityObject+SOGo.h"
|
#import "iCalRepeatableEntityObject+SOGo.h"
|
||||||
|
#import "iCalCalendar+SOGo.h"
|
||||||
|
|
||||||
@implementation iCalRepeatableEntityObject (SOGoExtensions)
|
@implementation iCalRepeatableEntityObject (SOGoExtensions)
|
||||||
|
|
||||||
|
@ -162,7 +163,7 @@
|
||||||
rule = [iCalRecurrenceRule new];
|
rule = [iCalRecurrenceRule new];
|
||||||
[rule setInterval: @"1"];
|
[rule setInterval: @"1"];
|
||||||
|
|
||||||
frequency = NSNotFound;
|
frequency = (int)NSNotFound;
|
||||||
o = [repeat objectForKey: @"frequency"];
|
o = [repeat objectForKey: @"frequency"];
|
||||||
if ([o isKindOfClass: [NSString class]])
|
if ([o isKindOfClass: [NSString class]])
|
||||||
{
|
{
|
||||||
|
|
|
@ -214,7 +214,7 @@
|
||||||
{
|
{
|
||||||
percent = [o intValue];
|
percent = [o intValue];
|
||||||
if (percent >= 0 && percent <= 100)
|
if (percent >= 0 && percent <= 100)
|
||||||
[self setPercentComplete: [NSString stringWithFormat: @"%i", percent]];
|
[self setPercentComplete: [NSString stringWithFormat: @"%i", (int)percent]];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
[self setPercentComplete: @""];
|
[self setPercentComplete: @""];
|
||||||
|
|
|
@ -327,7 +327,7 @@ convention:
|
||||||
|
|
||||||
if (year && month && day)
|
if (year && month && day)
|
||||||
[self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d",
|
[self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d",
|
||||||
year, month, day]];
|
(int)year, (int)month, (int)day]];
|
||||||
else
|
else
|
||||||
[self setBday: @""];
|
[self setBday: @""];
|
||||||
|
|
||||||
|
@ -644,11 +644,11 @@ convention:
|
||||||
birthDay = [[self bday] asCalendarDate];
|
birthDay = [[self bday] asCalendarDate];
|
||||||
if (birthDay)
|
if (birthDay)
|
||||||
{
|
{
|
||||||
stringValue = [NSString stringWithFormat: @"%.4d", [birthDay yearOfCommonEra]];
|
stringValue = [NSString stringWithFormat: @"%.4d", (int)[birthDay yearOfCommonEra]];
|
||||||
[self _setValue: @"birthyear" to: stringValue inLDIFRecord: ldifRecord];
|
[self _setValue: @"birthyear" to: stringValue inLDIFRecord: ldifRecord];
|
||||||
stringValue = [NSString stringWithFormat: @"%.2d", [birthDay monthOfYear]];
|
stringValue = [NSString stringWithFormat: @"%.2d", (int)[birthDay monthOfYear]];
|
||||||
[self _setValue: @"birthmonth" to: stringValue inLDIFRecord: ldifRecord];
|
[self _setValue: @"birthmonth" to: stringValue inLDIFRecord: ldifRecord];
|
||||||
stringValue = [NSString stringWithFormat: @"%.2d", [birthDay dayOfMonth]];
|
stringValue = [NSString stringWithFormat: @"%.2d", (int)[birthDay dayOfMonth]];
|
||||||
[self _setValue: @"birthday" to: stringValue inLDIFRecord: ldifRecord];
|
[self _setValue: @"birthday" to: stringValue inLDIFRecord: ldifRecord];
|
||||||
}
|
}
|
||||||
[self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord];
|
[self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord];
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
|
|
||||||
[response setHeader: [self davContentType] forKey: @"content-type"];
|
[response setHeader: [self davContentType] forKey: @"content-type"];
|
||||||
[response setHeader: [NSString stringWithFormat:@" %d",
|
[response setHeader: [NSString stringWithFormat:@" %d",
|
||||||
[data length]]
|
(int)[data length]]
|
||||||
forKey: @"content-length"];
|
forKey: @"content-length"];
|
||||||
[response setContent: data];
|
[response setContent: data];
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,9 +268,9 @@ Class SOGoContactSourceFolderK;
|
||||||
SOGoUser *currentUser;
|
SOGoUser *currentUser;
|
||||||
id <SOGoSource> source;
|
id <SOGoSource> source;
|
||||||
|
|
||||||
if ([sourceID isEqualToString: @"personal"])
|
if ([sourceID isEqualToString: @"personal"]){
|
||||||
result = [NSException exceptionWithHTTPStatus: 403
|
result = [NSException exceptionWithHTTPStatus: 403 reason: [NSString stringWithFormat: (@"folder '%@' cannot be deleted"), sourceID]];
|
||||||
reason: (@"folder '%@' cannot be deleted", sourceID)];
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = nil;
|
result = nil;
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
- (Class *) parsingClass
|
- (Class *) parsingClass
|
||||||
{
|
{
|
||||||
return [NGVCard class];
|
return (Class *)[NGVCard class];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* content */
|
/* content */
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
- (Class *) parsingClass
|
- (Class *) parsingClass
|
||||||
{
|
{
|
||||||
return [NGVList class];
|
return (Class *)[NGVList class];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,9 @@
|
||||||
#import <SOGo/NSObject+DAV.h>
|
#import <SOGo/NSObject+DAV.h>
|
||||||
#import <SOGo/SOGoPermissions.h>
|
#import <SOGo/SOGoPermissions.h>
|
||||||
#import <SOGo/SOGoSource.h>
|
#import <SOGo/SOGoSource.h>
|
||||||
|
#import <SOGo/SOGoUserManager.h>
|
||||||
#import <SOGo/SOGoUserSettings.h>
|
#import <SOGo/SOGoUserSettings.h>
|
||||||
|
#import <SOGo/SOGoSystemDefaults.h>
|
||||||
#import <SOGo/WORequest+SOGo.h>
|
#import <SOGo/WORequest+SOGo.h>
|
||||||
#import <SOGo/WOResponse+SOGo.h>
|
#import <SOGo/WOResponse+SOGo.h>
|
||||||
|
|
||||||
|
@ -94,7 +96,7 @@
|
||||||
{
|
{
|
||||||
if (![newDisplayName length])
|
if (![newDisplayName length])
|
||||||
newDisplayName = newName;
|
newDisplayName = newName;
|
||||||
ASSIGN (displayName, newDisplayName);
|
ASSIGN (displayName, (NSMutableString *)newDisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
@ -248,6 +250,13 @@
|
||||||
data = @"";
|
data = @"";
|
||||||
[newRecord setObject: data forKey: @"c_cn"];
|
[newRecord setObject: data forKey: @"c_cn"];
|
||||||
|
|
||||||
|
if ([[SOGoSystemDefaults sharedSystemDefaults] enableDomainBasedUID])
|
||||||
|
{
|
||||||
|
data = [oldRecord objectForKey: @"c_domain"];
|
||||||
|
if (data)
|
||||||
|
[newRecord setObject: data forKey: @"c_domain"];
|
||||||
|
}
|
||||||
|
|
||||||
// mail => emails[]
|
// mail => emails[]
|
||||||
data = [oldRecord objectForKey: @"c_emails"];
|
data = [oldRecord objectForKey: @"c_emails"];
|
||||||
if (data)
|
if (data)
|
||||||
|
@ -697,7 +706,7 @@
|
||||||
BOOL otherIsPersonal;
|
BOOL otherIsPersonal;
|
||||||
|
|
||||||
otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]]
|
otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]]
|
||||||
|| ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource]));
|
|| ([otherFolder isKindOfClass: object_getClass(self)] && [otherFolder isPersonalSource]));
|
||||||
|
|
||||||
if (isPersonalSource)
|
if (isPersonalSource)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/* NSObject+CardDAV.h - this file is part of SOGo
|
/* NSObject+CardDAV.h - this file is part of SOGo
|
||||||
*
|
*
|
||||||
* Copyright (C) 2007 Inverse inc.
|
* Copyright (C) 2007-2015 Inverse inc.
|
||||||
*
|
|
||||||
* Author: Ludovic Marcotte <ludovic@inverse.ca>
|
|
||||||
*
|
*
|
||||||
* This file is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/* NSObject+CardDAV.m - this file is part of SOGo
|
/* NSObject+CardDAV.m - this file is part of SOGo
|
||||||
*
|
*
|
||||||
* Copyright (C) 2007-2011 Inverse inc.
|
* Copyright (C) 2007-2015 Inverse inc.
|
||||||
*
|
|
||||||
* Author: Ludovic Marcotte <ludovic@inverse.ca>
|
|
||||||
*
|
*
|
||||||
* This file is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -187,6 +185,10 @@
|
||||||
[filters addObject: filter];
|
[filters addObject: filter];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no filters are provided, we return everything.
|
||||||
|
if (![filters count])
|
||||||
|
[filters addObject: [NSDictionary dictionaryWithObject: @"." forKey: @"mail"]];
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#import <Foundation/NSDictionary.h>
|
#import <Foundation/NSDictionary.h>
|
||||||
#import <Foundation/NSString.h>
|
#import <Foundation/NSString.h>
|
||||||
|
#import <NGExtensions/NSString+misc.h>
|
||||||
|
|
||||||
#import "NSDictionary+Mail.h"
|
#import "NSDictionary+Mail.h"
|
||||||
|
|
||||||
|
|
|
@ -533,7 +533,7 @@
|
||||||
messageID = [NSMutableString string];
|
messageID = [NSMutableString string];
|
||||||
[messageID appendFormat: @"<%@", [SOGoObject globallyUniqueObjectId]];
|
[messageID appendFormat: @"<%@", [SOGoObject globallyUniqueObjectId]];
|
||||||
pGUID = [[NSProcessInfo processInfo] globallyUniqueString];
|
pGUID = [[NSProcessInfo processInfo] globallyUniqueString];
|
||||||
[messageID appendFormat: @"@%u>", [pGUID hash]];
|
[messageID appendFormat: @"@%u>", (unsigned int)[pGUID hash]];
|
||||||
|
|
||||||
return [messageID lowercaseString];
|
return [messageID lowercaseString];
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
||||||
|
|
||||||
static NGMimeType *MultiMixedType = nil;
|
static NGMimeType *MultiMixedType = nil;
|
||||||
static NGMimeType *MultiAlternativeType = nil;
|
static NGMimeType *MultiAlternativeType = nil;
|
||||||
|
static NGMimeType *MultiRelatedType = nil;
|
||||||
static NSString *userAgent = nil;
|
static NSString *userAgent = nil;
|
||||||
|
|
||||||
+ (void) initialize
|
+ (void) initialize
|
||||||
|
@ -191,6 +192,9 @@ static NSString *userAgent = nil;
|
||||||
MultiAlternativeType = [NGMimeType mimeType: @"multipart" subType: @"alternative"];
|
MultiAlternativeType = [NGMimeType mimeType: @"multipart" subType: @"alternative"];
|
||||||
[MultiAlternativeType retain];
|
[MultiAlternativeType retain];
|
||||||
|
|
||||||
|
MultiRelatedType = [NGMimeType mimeType: @"multipart" subType: @"related"];
|
||||||
|
[MultiRelatedType retain];
|
||||||
|
|
||||||
userAgent = [NSString stringWithFormat: @"SOGoMail %@",
|
userAgent = [NSString stringWithFormat: @"SOGoMail %@",
|
||||||
SOGoVersion];
|
SOGoVersion];
|
||||||
[userAgent retain];
|
[userAgent retain];
|
||||||
|
@ -700,7 +704,8 @@ static NSString *userAgent = nil;
|
||||||
for (count = max - 1; count >= 0; count--)
|
for (count = max - 1; count >= 0; count--)
|
||||||
{
|
{
|
||||||
currentAddress = [addresses objectAtIndex: count];
|
currentAddress = [addresses objectAtIndex: count];
|
||||||
if ([currentRecipient
|
if (![currentAddress baseEMail] ||
|
||||||
|
[currentRecipient
|
||||||
caseInsensitiveCompare: [currentAddress baseEMail]]
|
caseInsensitiveCompare: [currentAddress baseEMail]]
|
||||||
== NSOrderedSame)
|
== NSOrderedSame)
|
||||||
{
|
{
|
||||||
|
@ -1676,16 +1681,20 @@ static NSString *userAgent = nil;
|
||||||
NGMimeMessage *message;
|
NGMimeMessage *message;
|
||||||
NGMutableHashMap *map;
|
NGMutableHashMap *map;
|
||||||
NSString *newText;
|
NSString *newText;
|
||||||
|
BOOL has_inline_images;
|
||||||
|
|
||||||
message = nil;
|
message = nil;
|
||||||
|
has_inline_images = NO;
|
||||||
bodyParts = [NSMutableArray array];
|
bodyParts = [NSMutableArray array];
|
||||||
|
|
||||||
if (_extractImages)
|
if (_extractImages)
|
||||||
{
|
{
|
||||||
newText = [text htmlByExtractingImages: bodyParts];
|
newText = [text htmlByExtractingImages: bodyParts];
|
||||||
if ([bodyParts count])
|
if ([bodyParts count])
|
||||||
[self setText: newText];
|
{
|
||||||
|
[self setText: newText];
|
||||||
|
has_inline_images = YES;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
map = [self mimeHeaderMapWithHeaders: _headers
|
map = [self mimeHeaderMapWithHeaders: _headers
|
||||||
|
@ -1702,10 +1711,20 @@ static NSString *userAgent = nil;
|
||||||
/* no attachments */
|
/* no attachments */
|
||||||
message = [self mimeMessageForContentWithHeaderMap: map];
|
message = [self mimeMessageForContentWithHeaderMap: map];
|
||||||
else
|
else
|
||||||
/* attachments, create multipart/mixed */
|
{
|
||||||
message = [self mimeMultiPartMessageWithHeaderMap: map
|
// attachments, create multipart/mixed or multipart/related if
|
||||||
andBodyParts: bodyParts];
|
// we have inline image to avoid Thunderbird bug #61815 (https://bugzilla.mozilla.org/show_bug.cgi?id=61815)
|
||||||
//[self debugWithFormat: @"message: %@", message];
|
if (has_inline_images)
|
||||||
|
{
|
||||||
|
[map removeAllObjectsForKey: @"content-type"];
|
||||||
|
[map addObject: MultiRelatedType forKey: @"content-type"];
|
||||||
|
}
|
||||||
|
|
||||||
|
message = [self mimeMultiPartMessageWithHeaderMap: map
|
||||||
|
andBodyParts: bodyParts];
|
||||||
|
|
||||||
|
//[self debugWithFormat: @"message: %@", message];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
|
|
@ -206,8 +206,8 @@ static NSString *inboxFolderName = @"INBOX";
|
||||||
[folders removeObjectsInArray: nss];
|
[folders removeObjectsInArray: nss];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [[folders stringsWithFormat: @"folder%@"]
|
return [[folders resultsOfSelector: @selector (asCSSIdentifier)]
|
||||||
resultsOfSelector: @selector (asCSSIdentifier)];
|
stringsWithFormat: @"folder%@"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *) toManyRelationshipKeys
|
- (NSArray *) toManyRelationshipKeys
|
||||||
|
|
|
@ -32,9 +32,11 @@
|
||||||
#import <NGExtensions/NSString+misc.h>
|
#import <NGExtensions/NSString+misc.h>
|
||||||
#import <NGExtensions/NSURL+misc.h>
|
#import <NGExtensions/NSURL+misc.h>
|
||||||
#import <NGImap4/NGImap4Connection.h>
|
#import <NGImap4/NGImap4Connection.h>
|
||||||
|
#import <NGImap4/NGImap4Client.h>
|
||||||
|
|
||||||
#import <SOGo/SOGoCache.h>
|
#import <SOGo/SOGoCache.h>
|
||||||
#import <SOGo/SOGoUser.h>
|
#import <SOGo/SOGoUser.h>
|
||||||
|
#import <SOGo/WORequest+SOGo.h>
|
||||||
|
|
||||||
#import "SOGoMailAccount.h"
|
#import "SOGoMailAccount.h"
|
||||||
#import "SOGoMailManager.h"
|
#import "SOGoMailManager.h"
|
||||||
|
|
|
@ -396,7 +396,7 @@ static BOOL debugOn = NO;
|
||||||
mimeType = @"application/octet-stream";
|
mimeType = @"application/octet-stream";
|
||||||
|
|
||||||
[response setHeader: mimeType forKey: @"content-type"];
|
[response setHeader: mimeType forKey: @"content-type"];
|
||||||
[response setHeader: [NSString stringWithFormat:@"%d", [data length]]
|
[response setHeader: [NSString stringWithFormat:@"%d", (int)[data length]]
|
||||||
forKey: @"content-length"];
|
forKey: @"content-length"];
|
||||||
|
|
||||||
if (asAttachment)
|
if (asAttachment)
|
||||||
|
|
|
@ -97,10 +97,10 @@
|
||||||
|
|
||||||
- (NSString *) davCollectionTag;
|
- (NSString *) davCollectionTag;
|
||||||
|
|
||||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||||
matchingSyncToken: (NSString *) theSyncToken
|
matchingSyncToken: (NSString *) syncToken
|
||||||
fromDate: (NSCalendarDate *) theStartDate;
|
fromDate: (NSCalendarDate *) theStartDate
|
||||||
|
initialLoad: (BOOL) initialLoadInProgress;
|
||||||
/* flags */
|
/* flags */
|
||||||
|
|
||||||
- (NSException *) addFlagsToAllMessages: (id) _f;
|
- (NSException *) addFlagsToAllMessages: (id) _f;
|
||||||
|
|
|
@ -199,9 +199,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
{
|
{
|
||||||
NSArray *subfolders;
|
NSArray *subfolders;
|
||||||
|
|
||||||
subfolders = [[self subfolders] stringsWithFormat: @"folder%@"];
|
subfolders = [[self subfolders] resultsOfSelector: @selector (asCSSIdentifier)];
|
||||||
|
|
||||||
return [subfolders resultsOfSelector: @selector (asCSSIdentifier)];
|
return [subfolders stringsWithFormat: @"folder%@"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *) subfolders
|
- (NSArray *) subfolders
|
||||||
|
@ -634,7 +634,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
inContext: (id) localContext
|
inContext: (id) localContext
|
||||||
{
|
{
|
||||||
NSArray *folders;
|
NSArray *folders;
|
||||||
NSString *currentFolderName, *currentAccountName;
|
NSString *currentFolderName, *currentAccountName, *destinationAccountName;
|
||||||
NSMutableString *imapDestinationFolder;
|
NSMutableString *imapDestinationFolder;
|
||||||
NGImap4Client *client;
|
NGImap4Client *client;
|
||||||
id result;
|
id result;
|
||||||
|
@ -642,24 +642,24 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
|
|
||||||
#warning this code will fail on implementation using something else than '/' as delimiter
|
#warning this code will fail on implementation using something else than '/' as delimiter
|
||||||
imapDestinationFolder = [NSMutableString string];
|
imapDestinationFolder = [NSMutableString string];
|
||||||
folders = [[destinationFolder componentsSeparatedByString: @"/"]
|
folders = [destinationFolder componentsSeparatedByString: @"/"];
|
||||||
resultsOfSelector: @selector (fromCSSIdentifier)];
|
|
||||||
max = [folders count];
|
max = [folders count];
|
||||||
if (max > 1)
|
if (max > 1)
|
||||||
{
|
{
|
||||||
currentAccountName = [[self mailAccountFolder] nameInContainer];
|
currentAccountName = [[self mailAccountFolder] nameInContainer];
|
||||||
client = [[self imap4Connection] client];
|
client = [[self imap4Connection] client];
|
||||||
[imap4 selectFolder: [self imap4URL]];
|
[imap4 selectFolder: [self imap4URL]];
|
||||||
|
destinationAccountName = [[folders objectAtIndex: 1] fromCSSIdentifier];
|
||||||
|
|
||||||
for (count = 2; count < max; count++)
|
for (count = 2; count < max; count++)
|
||||||
{
|
{
|
||||||
currentFolderName = [[folders objectAtIndex: count] substringFromIndex: 6];
|
currentFolderName = [[[folders objectAtIndex: count] substringFromIndex: 6] fromCSSIdentifier];
|
||||||
[imapDestinationFolder appendFormat: @"/%@", currentFolderName];
|
[imapDestinationFolder appendFormat: @"/%@", currentFolderName];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client)
|
if (client)
|
||||||
{
|
{
|
||||||
if ([[folders objectAtIndex: 1] isEqualToString: currentAccountName])
|
if ([destinationAccountName isEqualToString: currentAccountName])
|
||||||
{
|
{
|
||||||
// We make sure the destination IMAP folder exist, if not, we create it.
|
// We make sure the destination IMAP folder exist, if not, we create it.
|
||||||
result = [[client status: imapDestinationFolder
|
result = [[client status: imapDestinationFolder
|
||||||
|
@ -688,7 +688,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
|
|
||||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||||
accounts = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
accounts = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||||
account = [accounts lookupName: [folders objectAtIndex: 1] inContext: localContext acquire: NO];
|
account = [accounts lookupName: destinationAccountName inContext: localContext acquire: NO];
|
||||||
|
|
||||||
if ([account isKindOfClass: [NSException class]])
|
if ([account isKindOfClass: [NSException class]])
|
||||||
{
|
{
|
||||||
|
@ -1674,7 +1674,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
|
|
||||||
sortOrderings = [NSMutableArray array];
|
sortOrderings = [NSMutableArray array];
|
||||||
|
|
||||||
if ([self _sortElementIsAscending: sortElement])
|
if ([self _sortElementIsAscending: (NGDOMNodeWithChildren <DOMElement> *)sortElement])
|
||||||
sortOrderingOrder = EOCompareAscending;
|
sortOrderingOrder = EOCompareAscending;
|
||||||
else
|
else
|
||||||
sortOrderingOrder = EOCompareDescending;
|
sortOrderingOrder = EOCompareDescending;
|
||||||
|
@ -2106,7 +2106,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
// * 5 FETCH (UID 559 MODSEQ (17))
|
// * 5 FETCH (UID 559 MODSEQ (17))
|
||||||
// * 6 FETCH (UID 560 MODSEQ (18))
|
// * 6 FETCH (UID 560 MODSEQ (18))
|
||||||
// * 7 FETCH (UID 561 MODSEQ (19))
|
// * 7 FETCH (UID 561 MODSEQ (19))
|
||||||
|
//
|
||||||
//
|
//
|
||||||
// fetchUIDsOfVanishedItems ..
|
// fetchUIDsOfVanishedItems ..
|
||||||
//
|
//
|
||||||
|
@ -2119,6 +2119,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||||
matchingSyncToken: (NSString *) theSyncToken
|
matchingSyncToken: (NSString *) theSyncToken
|
||||||
fromDate: (NSCalendarDate *) theStartDate
|
fromDate: (NSCalendarDate *) theStartDate
|
||||||
|
initialLoad: (BOOL) initialLoadInProgress
|
||||||
{
|
{
|
||||||
EOQualifier *searchQualifier;
|
EOQualifier *searchQualifier;
|
||||||
NSMutableArray *allTokens;
|
NSMutableArray *allTokens;
|
||||||
|
@ -2195,6 +2196,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
|
|
||||||
for (i = 0; i < [fetchResults count]; i++)
|
for (i = 0; i < [fetchResults count]; i++)
|
||||||
{
|
{
|
||||||
|
if ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"] && initialLoadInProgress)
|
||||||
|
continue;
|
||||||
|
|
||||||
d = [NSDictionary dictionaryWithObject: ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"]) ? [NSNull null] : [[fetchResults objectAtIndex: i] objectForKey: @"modseq"]
|
d = [NSDictionary dictionaryWithObject: ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"]) ? [NSNull null] : [[fetchResults objectAtIndex: i] objectForKey: @"modseq"]
|
||||||
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
|
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
|
||||||
[allTokens addObject: d];
|
[allTokens addObject: d];
|
||||||
|
@ -2205,7 +2209,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||||
if (highestmodseq == 0)
|
if (highestmodseq == 0)
|
||||||
highestmodseq = 1;
|
highestmodseq = 1;
|
||||||
|
|
||||||
if (highestmodseq > 0)
|
if (highestmodseq > 0 && !initialLoadInProgress)
|
||||||
{
|
{
|
||||||
id uid;
|
id uid;
|
||||||
|
|
||||||
|
|
|
@ -73,10 +73,10 @@
|
||||||
|
|
||||||
- (NSString *) newLine
|
- (NSString *) newLine
|
||||||
{
|
{
|
||||||
NSString *rc = [NSString stringWithString: @" "];
|
NSString *rc = @" ";
|
||||||
|
|
||||||
if (htmlComposition)
|
if (htmlComposition)
|
||||||
rc = [NSString stringWithString: @"<br/>"];
|
rc = @"<br/>";
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
allLabels = [NSMutableArray array];
|
allLabels = [NSMutableArray array];
|
||||||
allKeys = [[theDefaults allKeys] sortedArrayUsingSelector: @selector (caseInsensitiveCompare:)];
|
allKeys = (NSMutableArray *)[[theDefaults allKeys] sortedArrayUsingSelector: @selector (caseInsensitiveCompare:)];
|
||||||
|
|
||||||
for (i = 0; i < [allKeys count]; i++)
|
for (i = 0; i < [allKeys count]; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -389,6 +389,13 @@ static BOOL debugSoParts = NO;
|
||||||
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
|
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
|
||||||
return info;
|
return info;
|
||||||
|
|
||||||
|
// deal with mails that contain only an attachment, for example:
|
||||||
|
// application/pkcs7-mime
|
||||||
|
// application/pdf
|
||||||
|
// etc.
|
||||||
|
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"application"])
|
||||||
|
return info;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
For each path component, eg 1,1,3
|
For each path component, eg 1,1,3
|
||||||
|
|
||||||
|
@ -775,9 +782,14 @@ static BOOL debugSoParts = NO;
|
||||||
[mimeType hasPrefix: @"audio/"] ||
|
[mimeType hasPrefix: @"audio/"] ||
|
||||||
[mimeType hasPrefix: @"image/"] ||
|
[mimeType hasPrefix: @"image/"] ||
|
||||||
[mimeType hasPrefix: @"video/"])
|
[mimeType hasPrefix: @"video/"])
|
||||||
|
{
|
||||||
filename = [NSString stringWithFormat: @"unknown_%@", path];
|
filename = [NSString stringWithFormat: @"unknown_%@", path];
|
||||||
else if ([mimeType isEqualToString: @"message/rfc822"])
|
}
|
||||||
filename = [NSString stringWithFormat: @"email_%@.eml", path];
|
else
|
||||||
|
{
|
||||||
|
if ([mimeType isEqualToString: @"message/rfc822"])
|
||||||
|
filename = [NSString stringWithFormat: @"email_%@.eml", path];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filename)
|
if (filename)
|
||||||
|
@ -810,7 +822,7 @@ static BOOL debugSoParts = NO;
|
||||||
NSMutableDictionary *currentPart;
|
NSMutableDictionary *currentPart;
|
||||||
NSString *newPath;
|
NSString *newPath;
|
||||||
NSArray *subparts;
|
NSArray *subparts;
|
||||||
NSString *type;
|
NSString *type, *subtype;
|
||||||
NSUInteger i;
|
NSUInteger i;
|
||||||
|
|
||||||
type = [[part objectForKey: @"type"] lowercaseString];
|
type = [[part objectForKey: @"type"] lowercaseString];
|
||||||
|
@ -821,19 +833,27 @@ static BOOL debugSoParts = NO;
|
||||||
{
|
{
|
||||||
currentPart = [subparts objectAtIndex: i-1];
|
currentPart = [subparts objectAtIndex: i-1];
|
||||||
if (path)
|
if (path)
|
||||||
newPath = [NSString stringWithFormat: @"%@.%d", path, i];
|
newPath = [NSString stringWithFormat: @"%@.%d", path, (int)i];
|
||||||
else
|
else
|
||||||
newPath = [NSString stringWithFormat: @"%d", i];
|
newPath = [NSString stringWithFormat: @"%d", (int)i];
|
||||||
[self _fetchFileAttachmentKeysInPart: currentPart
|
[self _fetchFileAttachmentKeysInPart: currentPart
|
||||||
intoArray: keys
|
intoArray: keys
|
||||||
withPath: newPath
|
withPath: newPath
|
||||||
andPrefix: [NSString stringWithFormat: @"%@/%i", prefix, i]];
|
andPrefix: [NSString stringWithFormat: @"%@/%i", prefix, (int)i]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!path)
|
if (!path)
|
||||||
path = @"1";
|
{
|
||||||
|
path = @"1";
|
||||||
|
|
||||||
|
// We set the path to 0 in case of a smime mail if not provided.
|
||||||
|
subtype = [[part objectForKey: @"subtype"] lowercaseString];
|
||||||
|
if ([subtype isEqualToString: @"pkcs7-mime"])
|
||||||
|
path = @"0";
|
||||||
|
}
|
||||||
|
|
||||||
[self _fetchFileAttachmentKey: part
|
[self _fetchFileAttachmentKey: part
|
||||||
intoArray: keys
|
intoArray: keys
|
||||||
withPath: path
|
withPath: path
|
||||||
|
@ -1027,6 +1047,14 @@ static BOOL debugSoParts = NO;
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Handles cases where the email is itself an attachment, so its Content-Type
|
||||||
|
// is application/*, image/* etc.
|
||||||
|
else if ([_key isEqualToString: @"asAttachment"] &&
|
||||||
|
(obj = [self lookupImap4BodyPartKey: @"0" inContext:_ctx]) != nil)
|
||||||
|
{
|
||||||
|
[obj setAsAttachment];
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
/* return 404 to stop acquisition */
|
/* return 404 to stop acquisition */
|
||||||
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
|
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
|
||||||
|
|
|
@ -310,7 +310,7 @@ static NSDictionary *BSONTypes()
|
||||||
|
|
||||||
case 'q': return 0x12;
|
case 'q': return 0x12;
|
||||||
default:
|
default:
|
||||||
[NSException raise: NSInvalidArgumentException format: @"%@::%s - invalid encoding type '%c'", [self class], _cmd, encoding];
|
[NSException raise: NSInvalidArgumentException format: @"%@::%@ - invalid encoding type '%c'", [self class], NSStringFromSelector(_cmd), encoding];
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -385,7 +385,7 @@ static NSDictionary *BSONTypes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[NSException raise: NSInvalidArgumentException format: @"%@::%s - invalid encoding type '%c'", [self class], _cmd, encoding];
|
[NSException raise: NSInvalidArgumentException format: @"%@::%@ - invalid encoding type '%c'", [self class], NSStringFromSelector(_cmd), encoding];
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1703,7 +1703,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||||
hostname: hostname
|
hostname: hostname
|
||||||
port: [NSString stringWithFormat: @"%d", port]
|
port: [NSString stringWithFormat: @"%d", port]
|
||||||
encryption: encryption
|
encryption: encryption
|
||||||
bindAsCurrentUser: NO];
|
bindAsCurrentUser: [NSString stringWithFormat: @"%d", NO]];
|
||||||
[ab setBaseDN: [entry dn]
|
[ab setBaseDN: [entry dn]
|
||||||
IDField: @"cn"
|
IDField: @"cn"
|
||||||
CNField: @"displayName"
|
CNField: @"displayName"
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/* NSArray+Utilities.h - this file is part of SOGo
|
/* NSArray+Utilities.h - this file is part of SOGo
|
||||||
*
|
*
|
||||||
* Copyright (C) 2006 Inverse inc.
|
* Copyright (C) 2006-2015 Inverse inc.
|
||||||
*
|
|
||||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
|
||||||
*
|
*
|
||||||
* This file is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/* NSArray+Utilities.m - this file is part of SOGo
|
/* NSArray+Utilities.m - this file is part of SOGo
|
||||||
*
|
*
|
||||||
* Copyright (C) 2006-2011 Inverse inc.
|
* Copyright (C) 2006-2015 Inverse inc.
|
||||||
*
|
|
||||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
|
||||||
*
|
*
|
||||||
* This file is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -90,9 +90,9 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
|
||||||
NSString *str;
|
NSString *str;
|
||||||
|
|
||||||
str = [NSString stringWithFormat: @"%.4d%.2d%.2d",
|
str = [NSString stringWithFormat: @"%.4d%.2d%.2d",
|
||||||
[self yearOfCommonEra],
|
(int)[self yearOfCommonEra],
|
||||||
[self monthOfYear],
|
(int)[self monthOfYear],
|
||||||
[self dayOfMonth]];
|
(int)[self dayOfMonth]];
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
@ -109,9 +109,9 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
|
||||||
|
|
||||||
return
|
return
|
||||||
[NSString stringWithFormat: @"%@, %.2d %@ %d %.2d:%.2d:%.2d %+.4d",
|
[NSString stringWithFormat: @"%@, %.2d %@ %d %.2d:%.2d:%.2d %+.4d",
|
||||||
rfc822Days[[self dayOfWeek]], [self dayOfMonth],
|
rfc822Days[[self dayOfWeek]], (int)[self dayOfMonth],
|
||||||
rfc822Months[[self monthOfYear]], [self yearOfCommonEra],
|
rfc822Months[[self monthOfYear]], (int)[self yearOfCommonEra],
|
||||||
[self hourOfDay], [self minuteOfHour], [self secondOfMinute],
|
(int)[self hourOfDay], (int)[self minuteOfHour], (int)[self secondOfMinute],
|
||||||
timeZoneShift];
|
timeZoneShift];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
* Boston, MA 02111-1307, USA.
|
* Boston, MA 02111-1307, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __OpenBSD__
|
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||||
#include <crypt.h>
|
#include <crypt.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
{
|
{
|
||||||
NSString *newTag;
|
NSString *newTag;
|
||||||
|
|
||||||
newTag = [NSString stringWithFormat: @"n%d", [namespaces count]];
|
newTag = [NSString stringWithFormat: @"n%d", (int)[namespaces count]];
|
||||||
[namespaces setObject: newTag forKey: newNS];
|
[namespaces setObject: newTag forKey: newNS];
|
||||||
|
|
||||||
return newTag;
|
return newTag;
|
||||||
|
|
|
@ -309,7 +309,7 @@ static int cssEscapingCount;
|
||||||
c == 0xD ||
|
c == 0xD ||
|
||||||
(c >= 0x20 && c <= 0xD7FF) ||
|
(c >= 0x20 && c <= 0xD7FF) ||
|
||||||
(c >= 0xE000 && c <= 0xFFFD) ||
|
(c >= 0xE000 && c <= 0xFFFD) ||
|
||||||
(c >= 0x10000 && c <= 0x10FFFF))
|
(c >= (unichar)0x10000 && c <= (unichar)0x10FFFF))
|
||||||
{
|
{
|
||||||
*(start+j) = c;
|
*(start+j) = c;
|
||||||
j++;
|
j++;
|
||||||
|
@ -373,6 +373,7 @@ static int cssEscapingCount;
|
||||||
|
|
||||||
- (NSString *) asCSSIdentifier
|
- (NSString *) asCSSIdentifier
|
||||||
{
|
{
|
||||||
|
NSCharacterSet *numericSet;
|
||||||
NSMutableString *cssIdentifier;
|
NSMutableString *cssIdentifier;
|
||||||
unichar currentChar;
|
unichar currentChar;
|
||||||
int count, max, idx;
|
int count, max, idx;
|
||||||
|
@ -381,10 +382,12 @@ static int cssEscapingCount;
|
||||||
[self _setupCSSEscaping];
|
[self _setupCSSEscaping];
|
||||||
|
|
||||||
cssIdentifier = [NSMutableString string];
|
cssIdentifier = [NSMutableString string];
|
||||||
|
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||||
max = [self length];
|
max = [self length];
|
||||||
|
|
||||||
if (max > 0)
|
if (max > 0)
|
||||||
{
|
{
|
||||||
if (isdigit([self characterAtIndex: 0]))
|
if ([numericSet characterIsMember: [self characterAtIndex: 0]])
|
||||||
// A CSS identifier can't start with a digit; we add an underscore
|
// A CSS identifier can't start with a digit; we add an underscore
|
||||||
[cssIdentifier appendString: @"_"];
|
[cssIdentifier appendString: @"_"];
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
|
@ -415,6 +418,7 @@ static int cssEscapingCount;
|
||||||
|
|
||||||
- (NSString *) fromCSSIdentifier
|
- (NSString *) fromCSSIdentifier
|
||||||
{
|
{
|
||||||
|
NSCharacterSet *numericSet;
|
||||||
NSMutableString *newString;
|
NSMutableString *newString;
|
||||||
NSString *currentString;
|
NSString *currentString;
|
||||||
int count, length, max, idx;
|
int count, length, max, idx;
|
||||||
|
@ -423,12 +427,14 @@ static int cssEscapingCount;
|
||||||
if (!cssEscapingStrings)
|
if (!cssEscapingStrings)
|
||||||
[self _setupCSSEscaping];
|
[self _setupCSSEscaping];
|
||||||
|
|
||||||
|
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||||
newString = [NSMutableString string];
|
newString = [NSMutableString string];
|
||||||
max = [self length];
|
max = [self length];
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|
||||||
if (max > 0
|
if (max > 0
|
||||||
&& [self characterAtIndex: 0] == '_'
|
&& [self characterAtIndex: 0] == '_'
|
||||||
&& isdigit([self characterAtIndex: 1]))
|
&& [numericSet characterIsMember: [self characterAtIndex: 1]])
|
||||||
{
|
{
|
||||||
/* If the identifier starts with an underscore followed by a digit,
|
/* If the identifier starts with an underscore followed by a digit,
|
||||||
we remove the underscore */
|
we remove the underscore */
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#ifndef SOGOCACHE_H
|
#ifndef SOGOCACHE_H
|
||||||
#define SOGOCACHE_H
|
#define SOGOCACHE_H
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
#import <Foundation/NSObject.h>
|
#import <Foundation/NSObject.h>
|
||||||
|
|
||||||
#include <libmemcached/memcached.h>
|
#include <libmemcached/memcached.h>
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
|
|
||||||
- (NSMutableString *) pathForChild: (NSString *) childName;
|
- (NSMutableString *) pathForChild: (NSString *) childName;
|
||||||
|
|
||||||
|
- (void) addUserInAcls: (NSString *) user;
|
||||||
|
|
||||||
- (NSArray *) toOneRelationshipKeys;
|
- (NSArray *) toOneRelationshipKeys;
|
||||||
- (NSArray *) toManyRelationshipKeys;
|
- (NSArray *) toManyRelationshipKeys;
|
||||||
|
|
||||||
|
|
|
@ -364,7 +364,7 @@ Class SOGoCacheGCSObjectK = Nil;
|
||||||
if (record)
|
if (record)
|
||||||
{
|
{
|
||||||
if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject)
|
if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject)
|
||||||
objectClass = isa;
|
objectClass = object_getClass(self);
|
||||||
else
|
else
|
||||||
objectClass = SOGoCacheGCSObjectK;
|
objectClass = SOGoCacheGCSObjectK;
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,9 @@ typedef enum {
|
||||||
|
|
||||||
- (void) reloadIfNeeded;
|
- (void) reloadIfNeeded;
|
||||||
- (void) save;
|
- (void) save;
|
||||||
|
- (NSException *) destroy;
|
||||||
|
|
||||||
|
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache;
|
||||||
|
|
||||||
/* accessors */
|
/* accessors */
|
||||||
- (NSMutableString *) path; /* full filename */
|
- (NSMutableString *) path; /* full filename */
|
||||||
|
|
|
@ -87,7 +87,7 @@ static EOAttribute *textColumn = nil;
|
||||||
{
|
{
|
||||||
tableUrl = nil;
|
tableUrl = nil;
|
||||||
initialized = NO;
|
initialized = NO;
|
||||||
objectType = -1;
|
objectType = (SOGoCacheObjectType) -1;
|
||||||
deleted = NO;
|
deleted = NO;
|
||||||
version = 0;
|
version = 0;
|
||||||
}
|
}
|
||||||
|
@ -103,11 +103,22 @@ static EOAttribute *textColumn = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer
|
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer
|
||||||
|
{
|
||||||
|
return [self objectWithName: key
|
||||||
|
inContainer: theContainer
|
||||||
|
useCache: YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache
|
||||||
{
|
{
|
||||||
SOGoCache *cache;
|
SOGoCache *cache;
|
||||||
id o;
|
id o;
|
||||||
|
|
||||||
cache = [SOGoCache sharedCache];
|
cache = [SOGoCache sharedCache];
|
||||||
|
|
||||||
|
if (!useCache)
|
||||||
|
[cache unregisterObjectWithName: key inContainer: theContainer];
|
||||||
|
|
||||||
o = [cache objectNamed: key inContainer: theContainer];
|
o = [cache objectNamed: key inContainer: theContainer];
|
||||||
|
|
||||||
if (!o)
|
if (!o)
|
||||||
|
@ -383,7 +394,7 @@ static EOAttribute *textColumn = nil;
|
||||||
@"SELECT * FROM %@ WHERE c_path = %@",
|
@"SELECT * FROM %@ WHERE c_path = %@",
|
||||||
tableName, pathValue];
|
tableName, pathValue];
|
||||||
if (startVersion > -1)
|
if (startVersion > -1)
|
||||||
[sql appendFormat: @" AND c_version > %d", startVersion];
|
[sql appendFormat: @" AND c_version > %d", (int)startVersion];
|
||||||
|
|
||||||
/* execution */
|
/* execution */
|
||||||
records = [self performSQLQuery: sql];
|
records = [self performSQLQuery: sql];
|
||||||
|
@ -411,15 +422,13 @@ static EOAttribute *textColumn = nil;
|
||||||
|
|
||||||
tableName = [self tableName];
|
tableName = [self tableName];
|
||||||
adaptor = [self tableChannelAdaptor];
|
adaptor = [self tableChannelAdaptor];
|
||||||
pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@", deviceId]
|
|
||||||
forAttribute: textColumn];
|
|
||||||
|
|
||||||
/* query */
|
/* query */
|
||||||
sql = [NSMutableString stringWithFormat:
|
sql = [NSMutableString stringWithFormat:
|
||||||
@"SELECT * FROM %@ WHERE c_type = %d AND c_deleted <> 1", tableName, objectType];
|
@"SELECT * FROM %@ WHERE c_type = %d AND c_deleted <> 1", tableName, objectType];
|
||||||
|
|
||||||
if (startVersion > -1)
|
if (startVersion > -1)
|
||||||
[sql appendFormat: @" AND c_version > %d", startVersion];
|
[sql appendFormat: @" AND c_version > %d", (int)startVersion];
|
||||||
|
|
||||||
if (deviceId) {
|
if (deviceId) {
|
||||||
pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@%", deviceId]
|
pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@%", deviceId]
|
||||||
|
@ -546,7 +555,7 @@ static EOAttribute *textColumn = nil;
|
||||||
|
|
||||||
lastModifiedValue = (NSInteger) [lastModified timeIntervalSince1970];
|
lastModifiedValue = (NSInteger) [lastModified timeIntervalSince1970];
|
||||||
|
|
||||||
if (objectType == -1)
|
if (objectType == (SOGoCacheObjectType) -1)
|
||||||
[NSException raise: @"SOGoCacheIOException"
|
[NSException raise: @"SOGoCacheIOException"
|
||||||
format: @"object type has not been set for object '%@'",
|
format: @"object type has not been set for object '%@'",
|
||||||
self];
|
self];
|
||||||
|
@ -576,7 +585,7 @@ static EOAttribute *textColumn = nil;
|
||||||
@")"),
|
@")"),
|
||||||
tableName,
|
tableName,
|
||||||
pathValue, parentPathValue, objectType,
|
pathValue, parentPathValue, objectType,
|
||||||
creationDateValue, lastModifiedValue,
|
(int)creationDateValue, (int)lastModifiedValue,
|
||||||
propsValue];
|
propsValue];
|
||||||
isNew = NO;
|
isNew = NO;
|
||||||
}
|
}
|
||||||
|
@ -590,7 +599,7 @@ static EOAttribute *textColumn = nil;
|
||||||
@" c_version = %d, c_content = %@"
|
@" c_version = %d, c_content = %@"
|
||||||
@" WHERE c_path = %@"),
|
@" WHERE c_path = %@"),
|
||||||
tableName,
|
tableName,
|
||||||
lastModifiedValue, deletedValue, version, propsValue,
|
(int)lastModifiedValue, (int)deletedValue, (int)version, propsValue,
|
||||||
pathValue];
|
pathValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,6 @@
|
||||||
- (NSCalendarDate *) creationDate;
|
- (NSCalendarDate *) creationDate;
|
||||||
- (NSCalendarDate *) lastModified;
|
- (NSCalendarDate *) lastModified;
|
||||||
|
|
||||||
- (NSException *) destroy;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#endif /* SOGOCACHEOBJECT_H */
|
#endif /* SOGOCACHEOBJECT_H */
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue