Compare commits
155
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f2eae5d67 | ||
|
|
9a7ea26b80 | ||
|
|
38c9a229c0 | ||
|
|
e75ec09445 | ||
|
|
58d03eedc3 | ||
|
|
98ad8f1c0b | ||
|
|
37b817cd68 | ||
|
|
9fd8f3edef | ||
|
|
ce11aca2bd | ||
|
|
861057f22f | ||
|
|
b910501afa | ||
|
|
063f878199 | ||
|
|
6bccd2eea0 | ||
|
|
652ae763d4 | ||
|
|
3da6c90bbe | ||
|
|
21a6480834 | ||
|
|
42f10e1929 | ||
|
|
03b706e1d8 | ||
|
|
2be556299e | ||
|
|
b075a306c6 | ||
|
|
36d8fa12d3 | ||
|
|
07092828fe | ||
|
|
7c0ba7b72b | ||
|
|
750a629df8 | ||
|
|
33a1d2c397 | ||
|
|
2e75972df6 | ||
|
|
1fdb44b0a7 | ||
|
|
3868ce0fb1 | ||
|
|
fc82eb79f6 | ||
|
|
9425868365 | ||
|
|
6280e4ded6 | ||
|
|
f19074334c | ||
|
|
b702e40ebb | ||
|
|
190f27ed7c | ||
|
|
cbc3e3e97d | ||
|
|
361d5ea3a6 | ||
|
|
24e3071e24 | ||
|
|
1f29198495 | ||
|
|
916442f9cf | ||
|
|
47859b76d6 | ||
|
|
3a60b6e38e | ||
|
|
ae3ac0a09a | ||
|
|
27a19ac956 | ||
|
|
27b9b7bfa7 | ||
|
|
18f758cb0a | ||
|
|
321672e2c3 | ||
|
|
8d9b54815c | ||
|
|
1dbfc18379 | ||
|
|
05b330e11b | ||
|
|
307876e6c7 | ||
|
|
ac6b299988 | ||
|
|
234db671ad | ||
|
|
2d33b01f31 | ||
|
|
490354427d | ||
|
|
22ce38808a | ||
|
|
82aca2cfe5 | ||
|
|
b17488ff25 | ||
|
|
ff3557598f | ||
|
|
26bd1b30fa | ||
|
|
0ce04fd4b6 | ||
|
|
f3656f7571 | ||
|
|
2aba083147 | ||
|
|
9414df26c5 | ||
|
|
7496b92da5 | ||
|
|
e38b0ac64f | ||
|
|
1404dbdb88 | ||
|
|
3dc8cc78ea | ||
|
|
72cbd9a45f | ||
|
|
10ac445f7c | ||
|
|
c5b5a4243d | ||
|
|
304ffa2099 | ||
|
|
e4ad255fc2 | ||
|
|
390a853471 | ||
|
|
dd9b5e18b6 | ||
|
|
5c017c19fc | ||
|
|
dec152e8e5 | ||
|
|
4922e6004d | ||
|
|
b91972d1dc | ||
|
|
5c3cdda186 | ||
|
|
4b19e64071 | ||
|
|
b6dee056ca | ||
|
|
372247a643 | ||
|
|
6830d6d930 | ||
|
|
366ad44cd1 | ||
|
|
9430f40101 | ||
|
|
b7c2d8ae95 | ||
|
|
f03e7a6604 | ||
|
|
4e7a3ce5fa | ||
|
|
61c542cb95 | ||
|
|
d2640e1501 | ||
|
|
20b91cc7bf | ||
|
|
b05f7a5beb | ||
|
|
3a61b7bae4 | ||
|
|
7bde181d3f | ||
|
|
1336a1d5a8 | ||
|
|
cf606e3224 | ||
|
|
c9f2fe2f85 | ||
|
|
10bc15d41d | ||
|
|
393b6d5128 | ||
|
|
68ae978b13 | ||
|
|
f4e3b98042 | ||
|
|
cfab18e1b8 | ||
|
|
959b4e015d | ||
|
|
7c6c78d13b | ||
|
|
7db109525d | ||
|
|
2ce85f6c5c | ||
|
|
7d2f96e96b | ||
|
|
3bf7afdfb7 | ||
|
|
503a70118f | ||
|
|
928b6b596e | ||
|
|
d0d93a297b | ||
|
|
936c38c43e | ||
|
|
7da7bdc1bf | ||
|
|
1e101ef01a | ||
|
|
3171257ef4 | ||
|
|
2c8d65843f | ||
|
|
62b16f4ef3 | ||
|
|
b86b1afdb9 | ||
|
|
8e047aaa9c | ||
|
|
dc2cc14d25 | ||
|
|
3f1a43cc13 | ||
|
|
67d0d95869 | ||
|
|
d16bf7c1a7 | ||
|
|
b527099907 | ||
|
|
72aab7f83f | ||
|
|
dfecce7381 | ||
|
|
82798ccb99 | ||
|
|
3809c002ed | ||
|
|
d9b4abc2f1 | ||
|
|
c1b0fd53dc | ||
|
|
c0a22ead1d | ||
|
|
d8e37cd7d8 | ||
|
|
7540cc3e33 | ||
|
|
d92b0341fb | ||
|
|
5e84f2817f | ||
|
|
c61eca27a1 | ||
|
|
96d88de564 | ||
|
|
90e11d76f6 | ||
|
|
3dd8bddc0e | ||
|
|
95f11f6d0d | ||
|
|
1ec53a063c | ||
|
|
cab5846e37 | ||
|
|
76f80c507c | ||
|
|
afed233131 | ||
|
|
b04b66fd79 | ||
|
|
13f2ac9cbb | ||
|
|
7dc333e790 | ||
|
|
cc62576fef | ||
|
|
a1d07c59af | ||
|
|
0a37652dd6 | ||
|
|
725dea5f6f | ||
|
|
76dd53b461 | ||
|
|
020c895925 | ||
|
|
f4963df50f | ||
|
|
7160d94e91 |
No files matched your search
+13
-28
@@ -1,33 +1,18 @@
|
||||
config.make
|
||||
tags
|
||||
*._*
|
||||
*.css
|
||||
*.pyc
|
||||
*.sax
|
||||
*.swp
|
||||
*.wox/
|
||||
*/*/*.SOGo/
|
||||
*/*/*/obj/
|
||||
*/*/obj/
|
||||
*/obj/
|
||||
.scss-lint-config.yml_
|
||||
Documentation/*.docbook
|
||||
Documentation/*.pdf
|
||||
*._*
|
||||
*/*/obj/
|
||||
*/*/*/obj/
|
||||
*/*/*.SOGo/
|
||||
*.sax
|
||||
*.wox/
|
||||
*.swp
|
||||
SoObjects/SOGo/SOGo.framework/
|
||||
SoObjects/SOGo/derived_src/
|
||||
Tests/*/config.py
|
||||
UI/WebServerResources/.bowerrc
|
||||
UI/WebServerResources/.sass-cache/
|
||||
UI/WebServerResources/bower_components/
|
||||
UI/WebServerResources/css/
|
||||
UI/WebServerResources/css/styles.css
|
||||
UI/WebServerResources/css/styles.css.map
|
||||
UI/WebServerResources/js/Common.js*
|
||||
UI/WebServerResources/js/Contacts.js*
|
||||
UI/WebServerResources/js/Mailer.js*
|
||||
UI/WebServerResources/js/Preferences.js*
|
||||
UI/WebServerResources/js/Scheduler.js*
|
||||
UI/WebServerResources/js/vendor/
|
||||
UI/WebServerResources/node_modules/
|
||||
UI/WebServerResources/scss/.sass-cache/
|
||||
config.make
|
||||
doc
|
||||
tags
|
||||
*.pyc
|
||||
._*
|
||||
Documentation/*.docbook
|
||||
Documentation/*.pdf
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "UI/WebServerResources/angular-material"]
|
||||
path = UI/WebServerResources/angular-material
|
||||
url = https://github.com/angular/material
|
||||
@@ -133,6 +133,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
|
||||
[[o properties] addEntriesFromDictionary: values];
|
||||
[o save];
|
||||
@@ -159,7 +160,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *nameInCache;
|
||||
|
||||
if (theFolderType == ActiveSyncMailFolder)
|
||||
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]];
|
||||
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
|
||||
else
|
||||
{
|
||||
NSString *component_name;
|
||||
@@ -170,7 +171,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
nameInCache= [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
}
|
||||
|
||||
return nameInCache;
|
||||
@@ -673,7 +674,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSDictionary *component;
|
||||
NSArray *allComponents;
|
||||
|
||||
BOOL updated;
|
||||
BOOL updated, initialLoadInProgress;
|
||||
int deleted, return_count;
|
||||
|
||||
if (theFolderType == ActiveSyncContactFolder)
|
||||
@@ -683,7 +684,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
|
||||
|
||||
|
||||
@@ -847,12 +861,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSMutableArray *allCacheObjects, *sortedBySequence;
|
||||
|
||||
SOGoMailObject *mailObject;
|
||||
NSArray *allMessages;
|
||||
NSArray *allMessages, *a;
|
||||
|
||||
int j, k, return_count;
|
||||
BOOL found_in_cache;
|
||||
int j, k, return_count, highestmodseq;
|
||||
BOOL found_in_cache, initialLoadInProgress;
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
highestmodseq = 0;
|
||||
|
||||
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
|
||||
}
|
||||
else
|
||||
{
|
||||
a = [theSyncKey componentsSeparatedByString: @"-"];
|
||||
highestmodseq = [[a objectAtIndex: 1] intValue];
|
||||
}
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||
max = [allMessages count];
|
||||
|
||||
allCacheObjects = [NSMutableArray array];
|
||||
@@ -911,7 +948,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *lastSequence;
|
||||
more_available = YES;
|
||||
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
||||
DESTROY(pool);
|
||||
|
||||
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@interface SOGoActiveSyncDispatcher : NSObject
|
||||
{
|
||||
NSURL *folderTableURL;
|
||||
NSDictionary *imapFolderGUIDS;
|
||||
id context;
|
||||
}
|
||||
|
||||
|
||||
@@ -151,12 +151,14 @@ static BOOL debugOn = NO;
|
||||
|
||||
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
||||
folderTableURL = nil;
|
||||
imapFolderGUIDS = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(folderTableURL);
|
||||
RELEASE(imapFolderGUIDS);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -229,23 +231,27 @@ static BOOL debugOn = NO;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
NSDictionary *imapGUIDs;
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
||||
|
||||
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
|
||||
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
if (!imapFolderGUIDS)
|
||||
{
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
|
||||
[imapFolderGUIDS retain];
|
||||
|
||||
}
|
||||
|
||||
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
}
|
||||
|
||||
return theIdToTranslate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -938,6 +944,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
[o save];
|
||||
|
||||
command_count++;
|
||||
@@ -1023,6 +1030,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
@@ -1046,6 +1054,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
@@ -1204,7 +1213,7 @@ static BOOL debugOn = NO;
|
||||
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
||||
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
|
||||
|
||||
count = [allMessages count];
|
||||
|
||||
|
||||
+5655
File diff suppressed because it is too large.
Load diff
@@ -1,157 +0,0 @@
|
||||
https://wiki.inverse.ca/focus/SOGo/CodingStyle
|
||||
https://wiki.inverse.ca/focus/SOGo/D%C3%A9veloppement
|
||||
https://wiki.inverse.ca/focus/PacketFence/Devel/BestPractices
|
||||
|
||||
UI and UX
|
||||
---------
|
||||
|
||||
* http://www.google.com/design/spec/material-design/
|
||||
* http://goodui.org/
|
||||
* Try Repeating Your Primary Action instead of showing it just once.
|
||||
* Try Distinct Clickable/Selected Styles instead of blurring them.
|
||||
* Try Undos instead of prompting for confirmation.
|
||||
* Try More Contrast instead of similarity.
|
||||
* Try Exposing Options instead of hiding them.
|
||||
* Try Showing State instead of being state agnostic.
|
||||
* Try Direct Manipulation instead of contextless menus.
|
||||
* Try Exposing Fields instead of creating extra pages.
|
||||
* Try Transitions instead of showing changes instantly.
|
||||
* Try Fewer Borders instead of wasting attention.
|
||||
* Try Designing For Zero Data instead of just data heavy cases.
|
||||
* Try Consistency instead of making people relearn.
|
||||
* Try Conventions instead of reinventing the wheel.
|
||||
* Try Visual Hierarchy instead of dullness.
|
||||
* Try Grouping Related Items instead of disordering.
|
||||
* Try Inline Validation instead of delaying errors.
|
||||
* Try Forgiving Inputs instead of being strict with data.
|
||||
* Try Bigger Click Areas instead of tiny ones.
|
||||
* Try Faster Load Times instead of making people wait.
|
||||
* Try Keyboard Shortcuts instead of buttons only.
|
||||
* Try Upfront Progress instead of starting with a blank.
|
||||
* Try Softer Prompts instead of modal windows.
|
||||
* Try Multifunctional Controls instead of more parts.
|
||||
* Try Icon Labels instead of opening for interpretation.
|
||||
* http://designmodo.com/create-style-guides/
|
||||
* http://uxmyths.com/
|
||||
|
||||
Frameworks & Tools
|
||||
------------------
|
||||
|
||||
GNUstep
|
||||
~~~~~~~
|
||||
|
||||
SOPE
|
||||
~~~~
|
||||
|
||||
AngularJS
|
||||
~~~~~~~~~
|
||||
|
||||
* https://angularjs.org/
|
||||
* http://angular-ui.github.io/ui-router/
|
||||
* https://www.airpair.com/angularjs/posts/angularjs-tutorial
|
||||
|
||||
Angular Material
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* https://material.angularjs.org/
|
||||
|
||||
Lo-Dash
|
||||
~~~~~~~
|
||||
|
||||
* http://lodash.com/
|
||||
* http://underscorejs.org/
|
||||
|
||||
Code Style
|
||||
----------
|
||||
|
||||
Objective-C
|
||||
~~~~~~~~~~~
|
||||
|
||||
* To document the Web APIs, we follow the http://apidocjs.com/[APIDOC] standards.
|
||||
|
||||
apidoc -f ".*\\.m" -i UI -o Documentation/JSON-API
|
||||
|
||||
HTML
|
||||
~~~~
|
||||
|
||||
* Localize your strings:
|
||||
|
||||
<var:string label:value="I'm localized!"/>
|
||||
|
||||
* Localize your attributes:
|
||||
|
||||
<input type="text" label:placeholder="I'm localized!"/>
|
||||
|
||||
* Reuse existing localized strings as much as possible. Otherwise don't forget to update the English Localizable.strings file of the appropriate module.
|
||||
|
||||
JavaScript
|
||||
~~~~~~~~~~
|
||||
|
||||
* http://eslint.org/
|
||||
* https://github.com/toddmotto/angularjs-styleguide
|
||||
* https://github.com/angular/material/blob/master/docs/guides/CODING.md
|
||||
|
||||
* We conform to https://github.com/airbnb/javascript[Airbnb coding standards]. Use https://github.com/jscs-dev/node-jscs[jscs] with the 'airbnb' preset to validate your code style:
|
||||
|
||||
jscs -p airbnb *.js
|
||||
|
||||
* For documentation, we follow the http://usejsdoc.org/[JSDoc3] standards:
|
||||
|
||||
jsdoc UI/WebServerResources/js/{Appointments,Common,Contacts,Mailer}/*.js -d Documentation/JavaScript-API
|
||||
|
||||
Models (facades)
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
////
|
||||
http://trochette.github.io/Angular-Design-Patterns-Best-Practices/
|
||||
////
|
||||
* Move business logic into models and share them.
|
||||
* Keep it simple, separate server interaction and error handling from the model. That way model only handle data processing and code is easier to maintain.
|
||||
|
||||
SASS/CSS
|
||||
~~~~~~~~
|
||||
|
||||
* http://componentcss.com/
|
||||
* https://github.com/styleguide/css
|
||||
* https://smacss.com/
|
||||
|
||||
* For documentation, we follow http://sassdoc.com/[SassDoc] annotations.
|
||||
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
* FlexBox compatibility http://caniuse.com/#feat=flexbox
|
||||
|
||||
Fonts
|
||||
^^^^^
|
||||
|
||||
* We build a custom icons font using the online app https://icomoon.io/app/#/select[icomoon].
|
||||
* http://www.carrois.com/fira-4-0/
|
||||
* Material Design Iconic Font http://zavoloklom.github.io/material-design-iconic-font/icons.html
|
||||
|
||||
Version Control
|
||||
---------------
|
||||
|
||||
* https://devcharm.com/articles/46/improve-your-git-workflow/
|
||||
* Each commit should cover only one thing;
|
||||
* Begin the commit message with a single short (less than 50 characters) line summarizing the change, followed by a blank line and then a more thorough description;
|
||||
* When fixing a bug, commit to the devel branch as well as the maintenance branch of the latest release version (named maintenance/x.y). When a ticket is associated to the bug, add to the description a line saying *Fixes #1234*.
|
||||
* `git pull` may introduce http://stackoverflow.com/questions/15316601/in-what-cases-could-git-pull-be-harmful[inconsistencies and problems]. Replace it with the following alias:
|
||||
|
||||
git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
* https://github.com/angular/protractor
|
||||
* http://karma-runner.github.io/
|
||||
|
||||
JSON API
|
||||
~~~~~~~~
|
||||
|
||||
One practical way to test the JSON API is to use `curl`. To do so, you need to enable `SOGoTrustProxyAuthentication` and configure HTTP authentifcation in Apache. You can pipe the result to [jq](http://stedolan.github.io/jq/) to nicely format and filter the output:
|
||||
|
||||
curl -u username:password http://localhost/SOGo/so/username/Calendar/calendarslist | jq '.'
|
||||
|
||||
curl -u username:password -H 'Content-Type: application/json' -d '{}' http://localhost/SOGo/so/francis/Calendar/personal/71B6-54904400-1-7C308500.ics/save
|
||||
@@ -233,6 +233,7 @@ Installation
|
||||
This section will guide you through the installation of the native
|
||||
Microsoft Outlook compatibility layer SOGo offers.
|
||||
|
||||
////
|
||||
Red Hat Enterprise Linux v6 x86_64
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -316,7 +317,7 @@ from this guide.
|
||||
On Ubuntu 12.04, the Samba init scripts need to be modified to
|
||||
disable the upstart check. For more details, refer to:
|
||||
https://wiki.samba.org/index.php/Samba4/InitScript
|
||||
|
||||
////
|
||||
|
||||
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
|
||||
----
|
||||
|
||||
You might consider changing the realm and domain used, to suit your
|
||||
enviroment.
|
||||
environment.
|
||||
|
||||
You might also have to
|
||||
remove `/etc/samba/smb.conf` prior running this command.
|
||||
@@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
|
||||
OpenChange Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
|
||||
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
|
||||
|
||||
First, create the OpenChange MySQL user:
|
||||
|
||||
@@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
|
||||
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
|
||||
----
|
||||
|
||||
////
|
||||
On RHEL, make sure SELinux is disabled:
|
||||
|
||||
setenforce 0
|
||||
////
|
||||
|
||||
Next, you can start Samba using the usual command:
|
||||
|
||||
@@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
|
||||
While HTTPS is not required to access this service, it is strongly
|
||||
recommended.
|
||||
|
||||
////
|
||||
On RHEL-based distributions, the apache configuration required by these
|
||||
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
|
||||
`/etc/httpd/conf.d/rpcproxy.conf`.
|
||||
////
|
||||
|
||||
For Debian-based distributions, these files can be found
|
||||
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
|
||||
@@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
|
||||
a2enconf ocsmanager
|
||||
a2enconf rpcproxy
|
||||
|
||||
////
|
||||
On RHEL-based distributions, make sure the `LoadModule` directive is
|
||||
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
|
||||
////
|
||||
|
||||
The _reqtimeout_ apache module is known to cause problems when using the
|
||||
default configuration shipped with Debian-based systems. On such
|
||||
@@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
|
||||
a2dismod reqtimeout
|
||||
|
||||
You should now restart the Apache service and make sure it will start on
|
||||
boot. On RHEL-based distributions, do:
|
||||
boot.
|
||||
////
|
||||
On RHEL-based distributions, do:
|
||||
|
||||
chkconfig httpd on && /etc/init.d/httpd restart
|
||||
|
||||
////
|
||||
On Debian-based distributions, do:
|
||||
|
||||
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
|
||||
@@ -813,7 +822,7 @@ the `openchange_user_cleanup` script distributed with SOGo. The script
|
||||
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
|
||||
RHEL).
|
||||
To reset a user, run the script as root:
|
||||
`python openchange_user_cleanup username`. See the usage output for additional options.
|
||||
`openchange_user_cleanup username`. See the usage output for additional options.
|
||||
* The "Out of Office Assistant" will not currently work. This feature
|
||||
has not been implemented.
|
||||
* Creating folders below INBOX (when not normally permitted by the IMAP
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- TODO have the build system take care of this -->
|
||||
<releaseinfo>Version 2.3.1 - July 2015</releaseinfo>
|
||||
<subtitle>for version 2.3.1</subtitle>
|
||||
<date>2015-07-23</date>
|
||||
<releaseinfo>Version 2.3.2 - September 2015</releaseinfo>
|
||||
<subtitle>for version 2.3.</subtitle>
|
||||
<date>2015-09-16</date>
|
||||
|
||||
<legalnotice>
|
||||
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
// TODO have the build system take care of this
|
||||
|
||||
:release_version: 2.3.1
|
||||
:release_version: 2.3.2
|
||||
|
||||
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
|
||||
@@ -1,12 +1,26 @@
|
||||
2.3.x (2015-MM-DD)
|
||||
2.3.2 (2015-09-16)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
|
||||
- improved EAS speed during initial syncing of large mailboxes (#3293)
|
||||
- updated CKEditor to version 4.5.3
|
||||
|
||||
Bug fixes
|
||||
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
|
||||
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- Fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- Folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- Use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
||||
- less sync issues when setting read flag (Zentyal)
|
||||
- attachments with non-latin filenames sent by Outlook are now received (Zentyal)
|
||||
- support attachments from more mail clients (Zentyal)
|
||||
- avoid conflicting message on saving a draft mail (Zentyal)
|
||||
- less conflicting messages in Outlook while moving messages between folders (Zentyal)
|
||||
- start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344)
|
||||
- fixed localization of calendar categories with empty profile (#3295)
|
||||
- fixed options availability in contextual menu of Contacts module (#3342)
|
||||
|
||||
2.3.1 (2015-07-23)
|
||||
------------------
|
||||
@@ -19,6 +33,7 @@ Enhancements
|
||||
- added create-folder subcommand to sogo-tool to create contact and calendar folders
|
||||
- group mail addresses can be used as recipient in Outlook
|
||||
- added 'ActiveSync' module constraints
|
||||
- updated CKEditor to version 4.5.1
|
||||
- added Slovenian translation - thanks to Jens Riecken
|
||||
- added Chinese (Taiwan) translation
|
||||
|
||||
|
||||
@@ -671,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
|
||||
return newAttachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#import "MAPIStoreDBFolder.h"
|
||||
#import "MAPIStoreDBMessage.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -104,6 +106,105 @@
|
||||
return objectVersion;
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList
|
||||
{
|
||||
BOOL updated;
|
||||
enum mapistore_error rc;
|
||||
NSData *currentChangeList, *changeKey;
|
||||
NSMutableArray *changeKeys;
|
||||
NSMutableData *newChangeList;
|
||||
NSUInteger count, len;
|
||||
struct SizedXid *changes;
|
||||
struct SPropValue property;
|
||||
struct SRow aRow;
|
||||
struct XID *currentChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t nChanges;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
|
||||
|
||||
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
if (!currentChangeList)
|
||||
{
|
||||
/* Create a new PredecessorChangeList */
|
||||
len = [changeKey length];
|
||||
newChangeList = [NSMutableData dataWithCapacity: len + 1];
|
||||
[newChangeList appendUInt8: len];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Update current predecessor change list with new change key */
|
||||
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
|
||||
with: &nChanges];
|
||||
|
||||
updated = NO;
|
||||
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
|
||||
for (count = 0; count < nChanges && !updated; count++)
|
||||
{
|
||||
if (GUID_equal(&changes[count].XID.NameSpaceGuid, ¤tChangeKey->NameSpaceGuid))
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
|
||||
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
|
||||
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
|
||||
{
|
||||
if ([globCnt length] != [oldGlobCnt length])
|
||||
{
|
||||
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
|
||||
abort();
|
||||
}
|
||||
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
|
||||
updated = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serialise it */
|
||||
changeKeys = [NSMutableArray array];
|
||||
|
||||
if (!updated)
|
||||
[changeKeys addObject: changeKey];
|
||||
|
||||
for (count = 0; count < nChanges; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithXID: &changes[count].XID];
|
||||
[changeKeys addObject: changeKey];
|
||||
}
|
||||
|
||||
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
|
||||
|
||||
newChangeList = [NSMutableData data];
|
||||
len = [changeKeys count];
|
||||
for (count = 0; count < len; count++)
|
||||
{
|
||||
changeKey = [changeKeys objectAtIndex: count];
|
||||
[newChangeList appendUInt8: [changeKey length]];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
}
|
||||
|
||||
if ([newChangeList length] > 0)
|
||||
{
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [self addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
//
|
||||
// FIXME: how this can happen?
|
||||
//
|
||||
@@ -166,6 +267,9 @@
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
||||
forKey: @"version"];
|
||||
|
||||
/* Update PredecessorChangeList accordingly */
|
||||
[self _updatePredecessorChangeList];
|
||||
|
||||
[self logWithFormat: @"%d props in dict", [properties count]];
|
||||
|
||||
[sogoObject save];
|
||||
@@ -209,4 +313,36 @@
|
||||
return [sogoObject lastModified];
|
||||
}
|
||||
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
/* Modify PidTagMessageFlags from SetMessageReadFlag and
|
||||
SyncImportReadStateChanges ROPs */
|
||||
NSNumber *flags;
|
||||
uint32_t newFlag;
|
||||
|
||||
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
if (flags)
|
||||
{
|
||||
newFlag = [flags unsignedLongValue];
|
||||
if (flag & SUPPRESS_RECEIPT)
|
||||
newFlag |= MSGFLAG_READ;
|
||||
if (flag & CLEAR_RN_PENDING)
|
||||
newFlag &= ~MSGFLAG_RN_PENDING;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag &= ~MSGFLAG_READ;
|
||||
if (flag & CLEAR_NRN_PENDING)
|
||||
newFlag &= ~MSGFLAG_NRN_PENDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
newFlag = MSGFLAG_READ;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag = 0x0;
|
||||
}
|
||||
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
|
||||
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -121,6 +121,7 @@
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) want_copy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
|
||||
|
||||
@@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMID: (uint64_t) targetMid
|
||||
andChangeKey: (struct Binary_r *) targetChangeKey
|
||||
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -669,15 +670,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
|
||||
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
|
||||
|
||||
if (targetChangeKey)
|
||||
if (targetPredecessorChangeList)
|
||||
{
|
||||
property.ulPropTag = PidTagChangeKey;
|
||||
property.value.bin = *targetChangeKey;
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *targetPredecessorChangeList;
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [destMsg addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
goto end;
|
||||
{
|
||||
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
[destMsg save: memCtx];
|
||||
if (!wantCopy)
|
||||
@@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
NSString *oldMessageURL;
|
||||
MAPIStoreMapping *mapping;
|
||||
SOGoUser *ownerUser;
|
||||
struct Binary_r *targetChangeKey;
|
||||
struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
|
||||
//TALLOC_CTX *memCtx;
|
||||
|
||||
//memCtx = talloc_zero (NULL, TALLOC_CTX);
|
||||
@@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
if (oldMessageURL)
|
||||
{
|
||||
[oldMessageURLs addObject: oldMessageURL];
|
||||
if (targetChangeKeys)
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
if (targetChangeKeys && targetPredecessorChangeList)
|
||||
{
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
targetPredecessorChangeList = targetPredecessorChangeLists[count];
|
||||
}
|
||||
else
|
||||
targetChangeKey = NULL;
|
||||
{
|
||||
targetChangeKey = NULL;
|
||||
targetPredecessorChangeList = NULL;
|
||||
}
|
||||
rc = [self _moveCopyMessageWithMID: srcMids[count]
|
||||
fromFolder: sourceFolder
|
||||
withMID: targetMids[count]
|
||||
andChangeKey: targetChangeKey
|
||||
andPredecessorChangeList: targetPredecessorChangeList
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
/* synchronisation */
|
||||
- (BOOL) synchroniseCache;
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey;
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl;
|
||||
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
|
||||
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
@@ -75,6 +75,7 @@ static Class NSNumberK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
@@ -261,7 +262,6 @@ static Class NSNumberK;
|
||||
*/
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
inChangeListOnly: (BOOL) inChangeListOnly
|
||||
{
|
||||
struct XID *xid;
|
||||
NSString *guid;
|
||||
@@ -270,19 +270,15 @@ static Class NSNumberK;
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
if (!inChangeListOnly)
|
||||
{
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
}
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
|
||||
/* 2. append/update predecessor change list */
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
@@ -296,6 +292,77 @@ static Class NSNumberK;
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
withOldChangeKey: (NSData *) oldChangeKey
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *changeKeyDict;
|
||||
NSString *guid;
|
||||
NSMutableDictionary *changeList;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid, *givenChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t i, length;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (predecessorChangeList)
|
||||
{
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldChangeKey)
|
||||
{
|
||||
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
|
||||
if (givenChangeKey) {
|
||||
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
|
||||
|
||||
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
|
||||
if (!changeKeyDict ||
|
||||
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
|
||||
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
|
||||
{
|
||||
/* The given change key is greater than current one stored in
|
||||
metadata or it does not exist */
|
||||
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil]
|
||||
forKey: @"ChangeKey"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
- (EOQualifier *) componentQualifier
|
||||
{
|
||||
if (!componentQualifier)
|
||||
@@ -465,8 +532,7 @@ static Class NSNumberK;
|
||||
// A GLOBCNT structure is a 6-byte global namespace counter,
|
||||
// we strip the first 2 bytes. The first two bytes is the ReplicaId
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: NO];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
}
|
||||
|
||||
now = [NSCalendarDate date];
|
||||
@@ -483,12 +549,13 @@ static Class NSNumberK;
|
||||
}
|
||||
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
|
||||
[self synchroniseCache];
|
||||
if (newChangeKey)
|
||||
if (oldChangeKey || pcl)
|
||||
{
|
||||
messages = [[versionsMessage properties] objectForKey: @"Messages"];
|
||||
messageEntry = [messages objectForKey: messageKey];
|
||||
@@ -496,8 +563,8 @@ static Class NSNumberK;
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"no version record found for message '%@'",
|
||||
messageKey];
|
||||
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: YES];
|
||||
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
|
||||
withOldChangeKey: oldChangeKey];
|
||||
[versionsMessage save];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,13 +209,16 @@
|
||||
|
||||
- (void) updateVersions
|
||||
{
|
||||
NSData *newChangeKey;
|
||||
/* Update ChangeKey and PredecessorChangeList on message's save */
|
||||
NSData *newChangeKey, *predecessorChangeList;
|
||||
|
||||
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
|
||||
|
||||
[(MAPIStoreGCSFolder *) container
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey];
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey
|
||||
andPredecessorChangeList: predecessorChangeList];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -30,6 +30,7 @@
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "MAPIStoreMailMessage.h"
|
||||
@@ -108,7 +109,7 @@
|
||||
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
|
||||
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
|
||||
0x68};
|
||||
|
||||
|
||||
*data = [[NSData dataWithBytes: recordBytes length: 16]
|
||||
asBinaryInMemCtx: memCtx];
|
||||
|
||||
@@ -117,19 +118,7 @@
|
||||
|
||||
- (NSString *) _fileName
|
||||
{
|
||||
NSString *fileName;
|
||||
NSDictionary *parameters;
|
||||
|
||||
fileName = [[bodyInfo objectForKey: @"parameterList"]
|
||||
objectForKey: @"name"];
|
||||
if (!fileName)
|
||||
{
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
fileName = [parameters objectForKey: @"filename"];
|
||||
}
|
||||
|
||||
return fileName;
|
||||
return [bodyInfo filename];
|
||||
}
|
||||
|
||||
- (int) getPidTagAttachLongFilename: (void **) data
|
||||
@@ -178,7 +167,7 @@
|
||||
|
||||
- (int) getPidTagAttachContentId: (void **) data
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
{
|
||||
*data = [[bodyInfo objectForKey: @"bodyId"]
|
||||
asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
|
||||
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
|
||||
#include <gen_ndr/exchange.h>
|
||||
|
||||
#undef DEBUG
|
||||
#include <util/attr.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
@@ -109,6 +111,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL) ensureFolderExists
|
||||
@@ -516,6 +519,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return [modseq1 compare: modseq2];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeListWith: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid;
|
||||
uint32_t i, length;
|
||||
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: NULL with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
talloc_free (sizedXIDList);
|
||||
}
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
@@ -526,8 +567,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
/* 1. set change key association */
|
||||
@@ -924,8 +965,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return changeNumber;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
NSString *messageUid;
|
||||
@@ -936,7 +976,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
messageEntry = [messages objectForKey: messageUid];
|
||||
if (!messageEntry)
|
||||
{
|
||||
[self warnWithFormat: @"attempting to synchronise to set the change key for "
|
||||
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
|
||||
@"this message %@", messageKey];
|
||||
synced = [self synchroniseCacheForUID: messageUid];
|
||||
if (synced)
|
||||
@@ -947,11 +987,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
|
||||
return messageEntry;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
[self _setChangeKey: changeKey
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
/* Update predecessor change list property given the change key. It
|
||||
returns if the change key has been added to the list or not */
|
||||
BOOL added = NO;
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *messageEntry;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct XID *xid;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
messageEntry = [self _messageEntryFromMessageKey: messageKey];
|
||||
if (messageEntry)
|
||||
{
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (changeList)
|
||||
{
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
{
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
[versionsMessage save];
|
||||
added = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"Missing predecessor change list to update"];
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
NSDictionary *messages, *changeKeyDict;
|
||||
@@ -1217,6 +1303,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
|
||||
@@ -1231,12 +1318,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
NSDictionary *result;
|
||||
NSUInteger count;
|
||||
NSArray *a;
|
||||
NSData *changeKey;
|
||||
NSData *changeList;
|
||||
|
||||
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
|
||||
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
|
||||
fromFolder: sourceFolder withMIDs: targetMids
|
||||
andChangeKeys: targetChangeKeys
|
||||
andPredecessorChangeLists: targetPredecessorChangeLists
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
|
||||
@@ -1325,11 +1413,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
[self synchroniseCache];
|
||||
for (count = 0; count < midCount; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
|
||||
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
|
||||
messageKey = [NSString stringWithFormat: @"%@.eml",
|
||||
[destUIDs objectAtIndex: count]];
|
||||
[self setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
[self _updatePredecessorChangeListWith: changeList
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#import <Mailer/NSData+Mail.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "Codepages.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
@@ -196,7 +197,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
count1 = [keys indexOfObject: data1];
|
||||
data2 = [entry2 objectForKey: @"mimeType"];
|
||||
count2 = [keys indexOfObject: data2];
|
||||
|
||||
|
||||
if (count1 == count2)
|
||||
{
|
||||
data1 = [entry1 objectForKey: @"key"];
|
||||
@@ -529,7 +530,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
else
|
||||
stringValue = @"";
|
||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -624,7 +625,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v = 0;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
|
||||
@@ -636,7 +637,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([[self attachmentKeys]
|
||||
count] > 0)
|
||||
v |= MSGFLAG_HASATTACH;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -656,7 +657,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
v = 2;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -668,15 +669,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
|
||||
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
if ([flags containsObject: @"flagged"])
|
||||
v = 6;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -755,7 +756,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
||||
{
|
||||
cn = [ngAddress displayName];
|
||||
|
||||
|
||||
// If we don't have a displayName, we use the email address instead. This
|
||||
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
|
||||
// nor in the recipient field if we reply to the email.
|
||||
@@ -809,7 +810,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
entryId = MAPIStoreExternalEntryId (cn, email);
|
||||
|
||||
*data = [entryId asBinaryInMemCtx: memCtx];
|
||||
|
||||
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
@@ -966,15 +967,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
{
|
||||
uint32_t v;
|
||||
NSString *s;
|
||||
|
||||
|
||||
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
|
||||
v = 0x1;
|
||||
|
||||
|
||||
if ([s hasPrefix: @"1"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"2"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"4"]) v = 0x0;
|
||||
else if ([s hasPrefix: @"5"]) v = 0x0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1163,14 +1164,14 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
|
||||
if ([headerMimeType isEqualToString: @"text/plain"])
|
||||
format = EDITOR_FORMAT_PLAINTEXT;
|
||||
else if ([headerMimeType isEqualToString: @"text/html"])
|
||||
format = EDITOR_FORMAT_HTML;
|
||||
else
|
||||
format = 0; /* EDITOR_FORMAT_DONTKNOW */
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, format);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1517,7 +1518,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
p = 0;
|
||||
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
|
||||
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
|
||||
|
||||
|
||||
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
|
||||
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
|
||||
p++;
|
||||
@@ -1525,7 +1526,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0);
|
||||
p++;
|
||||
|
||||
|
||||
// PR_7BIT_DISPLAY_NAME_UNICODE
|
||||
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
@@ -1533,7 +1534,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_SMTP_ADDRESS_UNICODE
|
||||
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
|
||||
|
||||
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
|
||||
p++;
|
||||
@@ -1566,12 +1567,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
withPrefix: (NSString *) keyPrefix
|
||||
{
|
||||
NSArray *parts;
|
||||
NSDictionary *parameters;
|
||||
NSUInteger count, max;
|
||||
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
if ([[parameters objectForKey: @"filename"] length] > 0)
|
||||
if ([[bodyInfo filename] length] > 0)
|
||||
{
|
||||
if ([keyPrefix length] == 0)
|
||||
keyPrefix = @"0";
|
||||
@@ -1645,15 +1643,29 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return attachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
BOOL modified = NO;
|
||||
BOOL alreadyRead = NO;
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
alreadyRead = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"]
|
||||
containsObject: @"seen"];
|
||||
|
||||
/* TODO: notifications should probably be emitted from here */
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
{
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
modified = alreadyRead;
|
||||
}
|
||||
else
|
||||
[sogoObject addFlags: imapFlag];
|
||||
{
|
||||
[sogoObject addFlags: imapFlag];
|
||||
modified = !alreadyRead;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
@@ -1694,7 +1706,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSNumber *value;
|
||||
|
||||
|
||||
@@ -161,15 +161,30 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
|
||||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
|
||||
if (modseq)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
{
|
||||
if (res->relop == RELOP_GT)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
|
||||
}
|
||||
else
|
||||
modseq = [NSNumber numberWithUnsignedLongLong: 0];
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
|
||||
if (res->relop == RELOP_GT || res->relop == RELOP_GE)
|
||||
{
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ignore other operations as IMAP only support MODSEQ >= X */
|
||||
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
|
||||
[self operatorFromRestrictionOperator: res->relop]];
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
#import <NGExtensions/NGHashMap.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSObject+Values.h>
|
||||
#import <NGExtensions/NSString+Encoding.h>
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
@@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
version = [properties objectForKey: @"version"];
|
||||
|
||||
return (version
|
||||
? exchange_globcnt ([version unsignedLongLongValue])
|
||||
? [version unsignedLongLongValue]
|
||||
: ULLONG_MAX);
|
||||
}
|
||||
|
||||
@@ -1110,7 +1111,8 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSString *folderName, *flag, *newIdString, *messageKey;
|
||||
BOOL updatedMetadata;
|
||||
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
|
||||
NSData *changeKey, *messageData;
|
||||
NGImap4Connection *connection;
|
||||
NGImap4Client *client;
|
||||
@@ -1146,21 +1148,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
[sogoObject setNameInContainer: messageKey];
|
||||
[mapping registerURL: [self url] withID: mid];
|
||||
|
||||
/* synchronise the cache and update the change key with the one provided
|
||||
by the client. Before doing this, lets issue a unselect/select combo
|
||||
because of timing issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if we do
|
||||
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
|
||||
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
|
||||
/* synchronise the cache and update the predecessor change list
|
||||
with the change key provided by the client. Before doing
|
||||
this, lets issue a unselect/select combo because of timing
|
||||
issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if
|
||||
we do a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)"
|
||||
command (where XYZ is the HIGHESTMODSEQ+1) immediately after
|
||||
IMAP APPEND */
|
||||
[client unselect];
|
||||
[client select: folderName];
|
||||
|
||||
[(MAPIStoreMailFolder *) container synchroniseCache];
|
||||
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
if (changeKey)
|
||||
[(MAPIStoreMailFolder *) container
|
||||
setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
{
|
||||
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
if (!updatedMetadata)
|
||||
[self warnWithFormat: @"Predecessor change list not updated with client data"];
|
||||
}
|
||||
|
||||
/* Update version property (PR_CHANGE_KEY indeed) as it is
|
||||
requested once it is saved */
|
||||
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
|
||||
if (changeNumber)
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
|
||||
forKey: @"version"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
withAID: (uint32_t) aid;
|
||||
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
|
||||
andRowCount: (uint32_t *) countPtr;
|
||||
- (int) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
|
||||
|
||||
- (NSArray *) activeContainerMessageTables;
|
||||
|
||||
@@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
|
||||
}
|
||||
|
||||
[self save: memCtx];
|
||||
/* We make sure that any change-related properties are removes from the
|
||||
/* We make sure that any change-related properties are removed from the
|
||||
properties dictionary, to make sure that related methods will be
|
||||
invoked the next time they are requested. */
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
|
||||
if ([container isKindOfClass: MAPIStoreFolderK])
|
||||
{
|
||||
@@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
|
||||
return [self getNo: data inMemCtx: memCtx];;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
// [self subclassResponsibility: _cmd];
|
||||
|
||||
|
||||
@@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
uint32_t mid_count,
|
||||
uint64_t *src_mids, uint64_t *t_mids,
|
||||
struct Binary_r **target_change_keys,
|
||||
struct Binary_r **target_predecessor_change_lists,
|
||||
uint8_t want_copy)
|
||||
{
|
||||
MAPIStoreFolder *sourceFolder, *targetFolder;
|
||||
@@ -698,6 +699,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
fromFolder: sourceFolder
|
||||
withMIDs: t_mids
|
||||
andChangeKeys: target_change_keys
|
||||
andPredecessorChangeLists: target_predecessor_change_lists
|
||||
wantCopy: want_copy
|
||||
inMemCtx: mem_ctx];
|
||||
TRYCATCH_END(pool)
|
||||
@@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
|
||||
struct MAPIStoreTallocWrapper *wrapper;
|
||||
NSAutoreleasePool *pool;
|
||||
MAPIStoreMessage *message;
|
||||
int rc;
|
||||
enum mapistore_error rc;
|
||||
|
||||
if (message_object)
|
||||
{
|
||||
|
||||
@@ -531,7 +531,7 @@
|
||||
}
|
||||
[vToDo setTimeStampAsDate: now];
|
||||
|
||||
[sogoObject saveComponent: vCalendar];
|
||||
[sogoObject saveCalendar: vCalendar];
|
||||
|
||||
[self updateVersions];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
|
||||
+ (id) dataWithXID: (const struct XID *) xid;
|
||||
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length;
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
#undef DEBUG
|
||||
#include <stdbool.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
#include <talloc.h>
|
||||
#include <util/time.h>
|
||||
#include <gen_ndr/exchange.h>
|
||||
@@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
NSMutableData *xidData;
|
||||
struct FlatUID_r flatUID;
|
||||
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->GUID);
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->NameSpaceGuid);
|
||||
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->Size];
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->LocalId.length];
|
||||
[xidData appendBytes: flatUID.ab length: 16];
|
||||
[xidData appendBytes: xid->Data length: xid->Size];
|
||||
[xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
|
||||
return xidData;
|
||||
}
|
||||
@@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
{
|
||||
xid = talloc_zero (memCtx, struct XID);
|
||||
|
||||
[self _extractGUID: &xid->GUID];
|
||||
[self _extractGUID: &xid->NameSpaceGuid];
|
||||
|
||||
xid->Size = max - 16;
|
||||
xid->LocalId.length = max - 16;
|
||||
|
||||
bytes = (uint8_t *) [self bytes];
|
||||
xid->Data = talloc_memdup (xid, (bytes+16), xid->Size);
|
||||
xid->LocalId.data = talloc_memdup (xid, (bytes+16), xid->LocalId.length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
return xid;
|
||||
}
|
||||
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length
|
||||
{
|
||||
struct Binary_r bin;
|
||||
struct SizedXid *sizedXIDArray;
|
||||
|
||||
bin.cb = [self length];
|
||||
bin.lpb = (uint8_t *)[self bytes];
|
||||
|
||||
sizedXIDArray = get_SizedXidArray(memCtx, &bin, length);
|
||||
if (!sizedXIDArray)
|
||||
{
|
||||
NSLog (@"Impossible to parse SizedXID array");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sizedXIDArray;
|
||||
}
|
||||
|
||||
- (NSComparisonResult) compare: (NSData *) otherGlobCnt
|
||||
{
|
||||
uint64_t globCnt = 0, oGlobCnt = 0;
|
||||
|
||||
if ([self length] > 0)
|
||||
globCnt = *(uint64_t *) [self bytes];
|
||||
|
||||
if ([otherGlobCnt length] > 0)
|
||||
oGlobCnt = *(uint64_t *) [otherGlobCnt bytes];
|
||||
|
||||
return MAPICNCompare (globCnt, oGlobCnt, NULL);
|
||||
}
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
{
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
[](http://www.sogo.nu/)
|
||||
|
||||
Share your calendars, address books and mails in your community with a completely free and open source solution.
|
||||
|
||||
Let your Mozilla Thunderbird/Lightning, Microsoft Outlook, Apple iCal/iPhone, Android and BlackBerry users collaborate using a modern platform.
|
||||
|
||||
## What is SOGo?
|
||||
|
||||
SOGo is [fully supported](http://www.sogo.nu/support/commercial_support.html) and [trusted](http://www.sogo.nu/tour/testimonials.html) groupware server with a focus on scalability and open standards. SOGo is released under the GNU GPL/LGPL v2 and above.
|
||||
|
||||
SOGo provides a rich AJAX-based Web interface and supports multiple native clients through the use of standard protocols such as CalDAV, CardDAV and GroupDAV.
|
||||
|
||||
SOGo is the missing component of your infrastructure; it sits in the middle of your servers to offer your users an uniform and complete interface to access their information. It has been deployed in production environments where thousands of users are involved.
|
||||
|
||||
[](http://www.sogo.nu/)
|
||||
|
||||
[Learn more...](http://www.sogo.nu/about/overview.html)
|
||||
|
||||
## Microsoft Outlook Compatible
|
||||
|
||||
SOGo offers native Microsoft Outlook compatibility using the [OpenChange](http://www.openchange.org/) backend developed by [Inverse](http://www.inverse.ca/). This means Microsoft Outlook 2003, 2007, 2010 and 2013 can talk directly to SOGo – just like if it was a Microsoft Exchange server. **No plugins are required in Outlook to make this work**.
|
||||
|
||||
The following screencast shows Outlook 2010 connected to SOGo as if it was an Exchange server, along the Web interface of SOGo.
|
||||
|
||||
[](http://www.youtube.com/watch?v=1hcBSB4Kxww)
|
||||
|
||||
[](https://github.com/inverse-inc/sogo/) [](https://www.ohloh.net/p/sogo) [](http://twitter.com/scalableogo) [](http://sourceforge.net/projects/sogo-zeg) [](https://freecode.com/projects/sogo-inverse) [](http://www.linkedin.com/groups/SOGo-Groupware-4164805)
|
||||
@@ -1,6 +1,8 @@
|
||||
/* iCalByDayMask.h - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2010-2015 Inverse inc.
|
||||
* Copyright (C) 2010 Wolfgang Sourdeau
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -53,8 +55,6 @@ typedef iCalWeekOccurrence iCalWeekOccurrences[7];
|
||||
- (id) initWithDays: (iCalWeekOccurrences) theDays;
|
||||
+ (id) byDayMaskWithRuleString: (NSString *) byDayRule;
|
||||
- (id) initWithRuleString: (NSString *) byDayRule;
|
||||
+ (id) byDayMaskWithDaysAndOccurrences: (NSArray *) values;
|
||||
- (id) initWithDaysAndOccurrences: (NSArray *) values;
|
||||
|
||||
- (BOOL) occursOnDay: (iCalWeekDay) weekDay;
|
||||
- (BOOL) occursOnDay: (iCalWeekDay) weekDay
|
||||
@@ -71,7 +71,6 @@ typedef iCalWeekOccurrence iCalWeekOccurrences[7];
|
||||
|
||||
- (NSString *) asRuleString;
|
||||
- (NSString *) asRuleStringWithIntegers;
|
||||
- (NSArray *) asRuleArray;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+20
-159
@@ -1,6 +1,8 @@
|
||||
/* iCalByDayMask.m - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2010-2015 Inverse inc.
|
||||
* Copyright (C) 2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,9 +21,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import "iCalByDayMask.h"
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
+ (id) byDayMaskWithDays: (iCalWeekOccurrences) theDays
|
||||
{
|
||||
id o;
|
||||
|
||||
|
||||
o = [[self alloc] initWithDays: theDays];
|
||||
AUTORELEASE(o);
|
||||
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
{
|
||||
id o;
|
||||
iCalWeekOccurrences d;
|
||||
|
||||
|
||||
d[iCalWeekDaySunday] = 0;
|
||||
d[iCalWeekDayMonday] = iCalWeekOccurrenceAll;
|
||||
d[iCalWeekDayTuesday] = iCalWeekOccurrenceAll;
|
||||
@@ -72,7 +72,7 @@
|
||||
d[iCalWeekDaySaturday] = 0;
|
||||
o = [[self alloc] initWithDays: d];
|
||||
AUTORELEASE(o);
|
||||
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
if (self)
|
||||
{
|
||||
memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
|
||||
|
||||
|
||||
if ([byDayRule length] > 0)
|
||||
{
|
||||
values = [byDayRule componentsSeparatedByString: @","];
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
[value getCharacters: chars
|
||||
range: NSMakeRange(valueLength - 2, 2)];
|
||||
|
||||
|
||||
switch (chars[0])
|
||||
{
|
||||
case 'M': day = iCalWeekDayMonday;
|
||||
@@ -142,7 +142,7 @@
|
||||
day = iCalWeekDaySunday;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (day != iCalWeekDayUnknown)
|
||||
{
|
||||
c = [value characterAtIndex: 0];
|
||||
@@ -157,15 +157,15 @@
|
||||
digitStart = 1;
|
||||
c = [value characterAtIndex: 1];
|
||||
}
|
||||
|
||||
|
||||
i = digitStart;
|
||||
|
||||
|
||||
while (i < valueLength && isdigit(c))
|
||||
{
|
||||
i++;
|
||||
c = [value characterAtIndex: i];
|
||||
}
|
||||
|
||||
|
||||
if (i != digitStart)
|
||||
order = [[value substringWithRange: NSMakeRange(digitStart, (i - digitStart))] intValue];
|
||||
|
||||
@@ -186,123 +186,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id) byDayMaskWithDaysAndOccurrences: (NSArray *) values
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [[self alloc] initWithDaysAndOccurrences: values];
|
||||
AUTORELEASE(o);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
- (id) initWithDaysAndOccurrences: (NSArray *) values
|
||||
{
|
||||
unsigned int count, max;
|
||||
NSString *value;
|
||||
unichar c, chars[2];
|
||||
unsigned int valueLength, i, digitStart, order;
|
||||
iCalWeekDay day;
|
||||
id mask;
|
||||
BOOL reverse;
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
|
||||
|
||||
max = [values count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
mask = [values objectAtIndex: count];
|
||||
if (![mask isKindOfClass: [NSDictionary class]])
|
||||
continue;
|
||||
value = [[mask objectForKey: @"day"] uppercaseString];
|
||||
valueLength = [value length];
|
||||
if (valueLength > 1)
|
||||
{
|
||||
day = iCalWeekDayUnknown;
|
||||
reverse = NO;
|
||||
digitStart = 0;
|
||||
order = 0;
|
||||
|
||||
[value getCharacters: chars
|
||||
range: NSMakeRange(0, valueLength)];
|
||||
|
||||
switch (chars[0])
|
||||
{
|
||||
case 'M': day = iCalWeekDayMonday;
|
||||
break;
|
||||
case 'W': day = iCalWeekDayWednesday;
|
||||
break;
|
||||
case 'F': day = iCalWeekDayFriday;
|
||||
break;
|
||||
case 'T':
|
||||
if (chars[1] == 'U')
|
||||
day = iCalWeekDayTuesday;
|
||||
else if (chars[1] == 'H')
|
||||
day = iCalWeekDayThursday;
|
||||
break;
|
||||
case 'S':
|
||||
if (chars[1] == 'A')
|
||||
day = iCalWeekDaySaturday;
|
||||
else if (chars[1] == 'U')
|
||||
day = iCalWeekDaySunday;
|
||||
break;
|
||||
}
|
||||
|
||||
if (day != iCalWeekDayUnknown)
|
||||
{
|
||||
value = [mask objectForKey: @"occurrence"];
|
||||
valueLength = [value length];
|
||||
if (valueLength > 0)
|
||||
{
|
||||
c = [value characterAtIndex: 0];
|
||||
if (c == '-')
|
||||
{
|
||||
digitStart = 1;
|
||||
reverse = YES;
|
||||
}
|
||||
else if (c == '+')
|
||||
{
|
||||
digitStart = 1;
|
||||
}
|
||||
|
||||
i = digitStart;
|
||||
while (i < valueLength)
|
||||
{
|
||||
c = [value characterAtIndex: i];
|
||||
i++;
|
||||
if (!isdigit(c))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i != digitStart)
|
||||
order = [[value substringWithRange: NSMakeRange(digitStart, (i - digitStart))] intValue];
|
||||
}
|
||||
|
||||
if (order > 0 && order < 6)
|
||||
{
|
||||
order = pow (2, order - 1);
|
||||
if (reverse)
|
||||
order = order << 5;
|
||||
days[day] |= order;
|
||||
//NSLog(@"*** iCalByDayMask [%i] %@ : day = %i, order = %i, result = %i", count, byDayRule, day, order, days[day]);
|
||||
}
|
||||
else
|
||||
{
|
||||
days[day] = iCalWeekOccurrenceAll;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -330,7 +214,7 @@
|
||||
withWeekNumber: (int) week
|
||||
{
|
||||
unsigned int absWeek, order;
|
||||
|
||||
|
||||
absWeek = abs (week);
|
||||
order = 0;
|
||||
if (absWeek > 0 && absWeek < 6)
|
||||
@@ -339,7 +223,7 @@
|
||||
if (week < 0)
|
||||
order = order << 5;
|
||||
}
|
||||
|
||||
|
||||
return ((days[weekDay] & order) > 0);
|
||||
}
|
||||
|
||||
@@ -365,7 +249,7 @@
|
||||
if (days[i])
|
||||
day = i;
|
||||
}
|
||||
|
||||
|
||||
return day;
|
||||
}
|
||||
|
||||
@@ -376,10 +260,10 @@
|
||||
|
||||
occurrence = 0;
|
||||
day = [self firstDay];
|
||||
|
||||
|
||||
if (day > -1 && days[day] != iCalWeekOccurrenceAll)
|
||||
occurrence = [self _iCalWeekOccurrenceIntValue: days[day]];
|
||||
|
||||
|
||||
return occurrence;
|
||||
}
|
||||
|
||||
@@ -439,7 +323,7 @@
|
||||
[rules addObject: rule];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [rules componentsJoinedByString: @","];
|
||||
}
|
||||
|
||||
@@ -460,27 +344,4 @@
|
||||
return s;
|
||||
}
|
||||
|
||||
- (NSArray *) asRuleArray
|
||||
{
|
||||
NSMutableArray *rules;
|
||||
NSMutableDictionary *rule;
|
||||
int i;
|
||||
|
||||
rules = [NSMutableArray array];
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
if (days[i])
|
||||
{
|
||||
rule = [NSMutableDictionary dictionary];
|
||||
if (days[i] != iCalWeekOccurrenceAll)
|
||||
[rule setObject: [NSNumber numberWithInt: [self _iCalWeekOccurrenceIntValue: days[i]]]
|
||||
forKey: @"occurrence"];
|
||||
[rule setObject: iCalWeekDayString[i]
|
||||
forKey: @"day"];
|
||||
[rules addObject: rule];
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
@end /* iCalByDayMask */
|
||||
@@ -94,8 +94,8 @@ typedef enum
|
||||
- (void) increaseSequence;
|
||||
|
||||
/* url can either be set as NSString or NSURL */
|
||||
- (void) setAttach: (NSArray *) _value;
|
||||
- (NSArray *) attach;
|
||||
- (void) setAttach: (id) _value;
|
||||
- (NSURL *) attach;
|
||||
- (void) setUrl: (id) _value;
|
||||
- (NSURL *) url;
|
||||
|
||||
|
||||
@@ -400,49 +400,32 @@
|
||||
return [self childrenWithTag: @"valarm"];
|
||||
}
|
||||
|
||||
- (void) setAttach: (NSArray *) _value
|
||||
- (void) setAttach: (id) _value
|
||||
{
|
||||
NSString *aString;
|
||||
id anObject;
|
||||
int count, max;
|
||||
|
||||
max = [_value count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
anObject = [_value objectAtIndex: count];
|
||||
if ([anObject isKindOfClass: [NSURL class]])
|
||||
{
|
||||
aString = [anObject absoluteString];
|
||||
}
|
||||
else
|
||||
aString = anObject;
|
||||
[self addChild: [CardElement simpleElementWithTag: @"attach" value: aString]];
|
||||
}
|
||||
if ([_value isKindOfClass: [NSString class]])
|
||||
aString = _value;
|
||||
else if ([_value isKindOfClass: [NSURL class]])
|
||||
aString = [_value absoluteString];
|
||||
else
|
||||
aString = @"";
|
||||
|
||||
[[self uniqueChildWithTag: @"attach"] setSingleValue: aString forKey: @""];
|
||||
}
|
||||
|
||||
- (NSArray *) attach
|
||||
- (NSURL *) attach
|
||||
{
|
||||
NSArray *elements;
|
||||
NSMutableArray *attachUrls;
|
||||
NSString *stringAttach;
|
||||
NSURL *url;
|
||||
int count, max;
|
||||
|
||||
elements = [self childrenWithTag: @"attach"];
|
||||
max = [elements count];
|
||||
attachUrls = [NSMutableArray arrayWithCapacity: max];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
stringAttach = [[elements objectAtIndex: count] flattenedValuesForKey: @""];
|
||||
url = [NSURL URLWithString: stringAttach];
|
||||
stringAttach = [[self uniqueChildWithTag: @"attach"] flattenedValuesForKey: @""];
|
||||
url = [NSURL URLWithString: stringAttach];
|
||||
|
||||
if (!url && [stringAttach length] > 0)
|
||||
url = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", stringAttach]];
|
||||
|
||||
[attachUrls addObject: [url absoluteString]];
|
||||
}
|
||||
if (!url && [stringAttach length] > 0)
|
||||
url = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", stringAttach]];
|
||||
|
||||
return attachUrls;
|
||||
return url;
|
||||
}
|
||||
|
||||
- (void) setUrl: (id) _value
|
||||
|
||||
@@ -44,8 +44,6 @@ typedef enum {
|
||||
|
||||
@interface iCalPerson : CardElement
|
||||
|
||||
+ (NSString *) descriptionForParticipationStatus: (iCalPersonPartStat) _status;
|
||||
|
||||
/* accessors */
|
||||
|
||||
- (void)setCn:(NSString *)_s;
|
||||
|
||||
+34
-45
@@ -25,48 +25,6 @@
|
||||
|
||||
@implementation iCalPerson
|
||||
|
||||
+ (NSString *) descriptionForParticipationStatus: (iCalPersonPartStat) _status
|
||||
{
|
||||
NSString *stat;
|
||||
|
||||
switch (_status) {
|
||||
case iCalPersonPartStatUndefined:
|
||||
stat = @"";
|
||||
break;
|
||||
case iCalPersonPartStatAccepted:
|
||||
stat = @"ACCEPTED";
|
||||
break;
|
||||
case iCalPersonPartStatDeclined:
|
||||
stat = @"DECLINED";
|
||||
break;
|
||||
case iCalPersonPartStatTentative:
|
||||
stat = @"TENTATIVE";
|
||||
break;
|
||||
case iCalPersonPartStatDelegated:
|
||||
stat = @"DELEGATED";
|
||||
break;
|
||||
case iCalPersonPartStatCompleted:
|
||||
stat = @"COMPLETED";
|
||||
break;
|
||||
case iCalPersonPartStatInProcess:
|
||||
stat = @"IN-PROCESS";
|
||||
break;
|
||||
case iCalPersonPartStatExperimental:
|
||||
case iCalPersonPartStatOther:
|
||||
// [NSException raise:NSInternalInconsistencyException
|
||||
// format:@"Attempt to set meaningless "
|
||||
// @"participationStatus (%d)!", _status];
|
||||
stat = nil; /* keep compiler happy */
|
||||
break;
|
||||
default:
|
||||
stat = @"NEEDS-ACTION";
|
||||
break;
|
||||
}
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
|
||||
/* accessors */
|
||||
|
||||
- (void) setCn: (NSString *) _s
|
||||
@@ -174,13 +132,44 @@
|
||||
{
|
||||
NSString *stat;
|
||||
|
||||
stat = [iCalPerson descriptionForParticipationStatus: _status];
|
||||
|
||||
switch (_status) {
|
||||
case iCalPersonPartStatUndefined:
|
||||
stat = @"";
|
||||
break;
|
||||
case iCalPersonPartStatAccepted:
|
||||
stat = @"ACCEPTED";
|
||||
break;
|
||||
case iCalPersonPartStatDeclined:
|
||||
stat = @"DECLINED";
|
||||
break;
|
||||
case iCalPersonPartStatTentative:
|
||||
stat = @"TENTATIVE";
|
||||
break;
|
||||
case iCalPersonPartStatDelegated:
|
||||
stat = @"DELEGATED";
|
||||
break;
|
||||
case iCalPersonPartStatCompleted:
|
||||
stat = @"COMPLETED";
|
||||
break;
|
||||
case iCalPersonPartStatInProcess:
|
||||
stat = @"IN-PROCESS";
|
||||
break;
|
||||
case iCalPersonPartStatExperimental:
|
||||
case iCalPersonPartStatOther:
|
||||
// [NSException raise:NSInternalInconsistencyException
|
||||
// format:@"Attempt to set meaningless "
|
||||
// @"participationStatus (%d)!", _status];
|
||||
stat = nil; /* keep compiler happy */
|
||||
break;
|
||||
default:
|
||||
stat = @"NEEDS-ACTION";
|
||||
break;
|
||||
}
|
||||
if (stat)
|
||||
[self setPartStat:stat];
|
||||
}
|
||||
|
||||
- (iCalPersonPartStat) participationStatus {
|
||||
- (iCalPersonPartStat)participationStatus {
|
||||
NSString *stat;
|
||||
|
||||
stat = [[self partStat] uppercaseString];
|
||||
|
||||
@@ -75,7 +75,6 @@ extern NSString *iCalWeekDayString[];
|
||||
- (void) setFrequency: (iCalRecurrenceFrequency) _frequency;
|
||||
- (iCalRecurrenceFrequency) frequency;
|
||||
- (iCalRecurrenceFrequency) valueForFrequency: (NSString *) value;
|
||||
- (NSString *) frequencyForValue: (iCalRecurrenceFrequency) freq;
|
||||
|
||||
- (void) setRepeatInterval: (int) _repeatInterval;
|
||||
- (int) repeatInterval;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalTimeZone.m - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
* Copyright (C) 2006-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -147,8 +147,7 @@ static NSArray *knownTimeZones;
|
||||
if (ext.location != NSNotFound)
|
||||
{
|
||||
zone = [zone substringToIndex: ext.location];
|
||||
if (![timeZoneNames containsObject: zone])
|
||||
[timeZoneNames addObject: zone];
|
||||
[timeZoneNames addObject: zone];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,11 @@
|
||||
minute: [tzStart minuteOfHour] second: 0
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
|
||||
|
||||
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1)
|
||||
day: 0 hour: 0 minute: 0
|
||||
tmpDate = [tmpDate addYear: 0
|
||||
month: ((pos > 0) ? 0 : 1)
|
||||
day: 0
|
||||
hour: 0
|
||||
minute: 0
|
||||
second: 0];
|
||||
|
||||
/* If the day of the time change is "-XSU", we need to determine whether the
|
||||
@@ -197,12 +200,41 @@
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
|
||||
The time changes occure on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
The time changes occur on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
|
||||
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
|
||||
effectively shift the time change by a whole week.
|
||||
|
||||
In Europe/Berlin, we have a different use-case for November. In 2015, November 1st is a Sunday.
|
||||
The time change in November must occur on October 25th but since tmpDate will be November 1st,
|
||||
so a Sunday, dateDayOfWeek will be 0 and dayOfWeek will also be 0 we would decrement tmpDate by 14 days,
|
||||
which is incorrect because it would shift the timezone change one week earlier. We take care about this
|
||||
one with check if pos is greater or equal than 0 and if so, we don't decrement it.
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Inverse inc.//NONSGML Olson 2014g//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR
|
||||
*/
|
||||
if (dayOfWeek == dateDayOfWeek)
|
||||
if (dayOfWeek == dateDayOfWeek && pos >= 0)
|
||||
pos--;
|
||||
|
||||
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
- (NSString *) percentComplete;
|
||||
|
||||
- (void) setDue: (NSCalendarDate *) _date;
|
||||
- (void) setAllDayDue: (NSCalendarDate *) _date;
|
||||
- (NSCalendarDate *) due;
|
||||
|
||||
- (void) setCompleted: (NSCalendarDate *) _date;
|
||||
|
||||
@@ -65,12 +65,6 @@
|
||||
setDateTime: newDueDate];
|
||||
}
|
||||
|
||||
- (void) setAllDayDue: (NSCalendarDate *) newDueDate
|
||||
{
|
||||
[(iCalDateTime *) [self uniqueChildWithTag: @"due"]
|
||||
setDate: newDueDate];
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) due
|
||||
{
|
||||
return [(iCalDateTime *) [self uniqueChildWithTag: @"due"]
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
|
||||
- (NSCalendarDate *) nextAlarmDate;
|
||||
|
||||
- (NSDictionary *) asDictionary;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __NGCards_iCalTrigger_H__ */
|
||||
@@ -20,9 +20,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import "iCalEvent.h"
|
||||
#import "iCalTrigger.h"
|
||||
@@ -54,78 +52,6 @@
|
||||
return [self value: 0 ofAttribute: @"related"];
|
||||
}
|
||||
|
||||
- (NSDictionary *) asDictionary
|
||||
{
|
||||
NSDictionary *data;
|
||||
NSString *duration, *relation, *reference, *quantity, *unit;
|
||||
NSUInteger i;
|
||||
unichar c;
|
||||
|
||||
data = nil;
|
||||
if (![[self valueType] length] ||
|
||||
[[self valueType] caseInsensitiveCompare: @"DURATION"] == NSOrderedSame)
|
||||
{
|
||||
relation = [[self relationType] uppercaseString];
|
||||
duration = [self flattenedValuesForKey: @""];
|
||||
i = 0;
|
||||
c = [duration characterAtIndex: i];
|
||||
if (c == '-')
|
||||
{
|
||||
reference = @"BEFORE";
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
reference = @"AFTER";
|
||||
}
|
||||
c = [duration characterAtIndex: i];
|
||||
if (c == 'P')
|
||||
{
|
||||
quantity = @"";
|
||||
unit = @"";
|
||||
// Parse duration -- ignore first character (P)
|
||||
for (i++; i < [duration length]; i++)
|
||||
{
|
||||
c = [duration characterAtIndex: i];
|
||||
if (c == 't' || c == 'T')
|
||||
// time -- ignore character
|
||||
continue;
|
||||
else if (isdigit (c))
|
||||
quantity = [quantity stringByAppendingFormat: @"%c", c];
|
||||
else
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'D': /* day */
|
||||
unit = @"DAYS";
|
||||
break;
|
||||
case 'H': /* hour */
|
||||
unit = @"HOURS";
|
||||
break;
|
||||
case 'M': /* min */
|
||||
unit = @"MINUTES";
|
||||
break;
|
||||
case 'W': /* week */
|
||||
unit = @"WEEKS";
|
||||
break;
|
||||
default:
|
||||
NSLog(@"Cannot process duration unit: '%c'", c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
relation, @"relation",
|
||||
reference, @"reference",
|
||||
[NSNumber numberWithInt: [quantity intValue]], @"quantity",
|
||||
unit, @"unit",
|
||||
nil];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) nextAlarmDate
|
||||
{
|
||||
NSCalendarDate *relationDate, *nextAlarmDate;
|
||||
|
||||
@@ -65,7 +65,6 @@ typedef enum {
|
||||
NSMutableDictionary *uidToFilename;
|
||||
NSMutableDictionary *aclMatrix;
|
||||
NSMutableArray *stripFields;
|
||||
NSString *baseCalDAVURL, *basePublicCalDAVURL;
|
||||
int davCalendarStartTimeLimit;
|
||||
int davTimeLimitSeconds;
|
||||
int davTimeHalfLimitSeconds;
|
||||
@@ -74,7 +73,6 @@ typedef enum {
|
||||
}
|
||||
|
||||
- (BOOL) isActive;
|
||||
- (BOOL) isWebCalendar;
|
||||
|
||||
- (NSString *) calendarColor;
|
||||
- (void) setCalendarColor: (NSString *) newColor;
|
||||
@@ -131,13 +129,6 @@ typedef enum {
|
||||
- (NSString *) baseURLForAptWithUID: (NSString *) _uid
|
||||
inContext: (id) _ctx;
|
||||
|
||||
- (NSString *) calDavURL;
|
||||
- (NSString *) webDavICSURL;
|
||||
- (NSString *) webDavXMLURL;
|
||||
- (NSString *) publicCalDavURL;
|
||||
- (NSString *) publicWebDavICSURL;
|
||||
- (NSString *) publicWebDavXMLURL;
|
||||
|
||||
/* folder management */
|
||||
|
||||
- (id) lookupHomeFolderForUID: (NSString *) _uid
|
||||
@@ -166,6 +157,12 @@ typedef enum {
|
||||
- (BOOL) showCalendarTasks;
|
||||
- (void) setShowCalendarTasks: (BOOL) new;
|
||||
|
||||
- (NSString *) syncTag;
|
||||
- (void) setSyncTag: (NSString *) newSyncTag;
|
||||
|
||||
- (BOOL) synchronizeCalendar;
|
||||
- (void) setSynchronizeCalendar: (BOOL) new;
|
||||
|
||||
- (BOOL) includeInFreeBusy;
|
||||
- (void) setIncludeInFreeBusy: (BOOL) newInclude;
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
#import "SOGoAppointmentFolders.h"
|
||||
#import "SOGoFreeBusyObject.h"
|
||||
#import "SOGoTaskObject.h"
|
||||
#import "SOGoWebAppointmentFolder.h";
|
||||
|
||||
#import "SOGoAppointmentFolder.h"
|
||||
|
||||
@@ -333,15 +332,13 @@ static Class iCalEventK = nil;
|
||||
memset (userCanAccessObjectsClassifiedAs, NO,
|
||||
iCalAccessClassCount * sizeof (BOOL));
|
||||
|
||||
davCalendarStartTimeLimit = [[user domainDefaults] davCalendarStartTimeLimit];
|
||||
davCalendarStartTimeLimit
|
||||
= [[user domainDefaults] davCalendarStartTimeLimit];
|
||||
davTimeLimitSeconds = davCalendarStartTimeLimit * 86400;
|
||||
/* 86400 / 2 = 43200. We hardcode that value in order to avoid
|
||||
integer and float confusion. */
|
||||
davTimeHalfLimitSeconds = davCalendarStartTimeLimit * 43200;
|
||||
componentSet = nil;
|
||||
|
||||
baseCalDAVURL = nil;
|
||||
basePublicCalDAVURL = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -353,8 +350,6 @@ static Class iCalEventK = nil;
|
||||
[stripFields release];
|
||||
[uidToFilename release];
|
||||
[componentSet release];
|
||||
[baseCalDAVURL release];
|
||||
[basePublicCalDAVURL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -437,6 +432,61 @@ static Class iCalEventK = nil;
|
||||
inCategory: @"FolderShowTasks"];
|
||||
}
|
||||
|
||||
- (NSString *) syncTag
|
||||
{
|
||||
NSString *syncTag;
|
||||
|
||||
syncTag = [self folderPropertyValueInCategory: @"FolderSyncTags"];
|
||||
if (!syncTag)
|
||||
syncTag = @"";
|
||||
|
||||
return syncTag;
|
||||
}
|
||||
|
||||
- (void) setSyncTag: (NSString *) newSyncTag
|
||||
{
|
||||
// Check for duplicated tags
|
||||
SOGoUserSettings *settings;
|
||||
NSDictionary *syncTags;
|
||||
NSArray *values;
|
||||
|
||||
if ([newSyncTag length])
|
||||
{
|
||||
settings = [[context activeUser] userSettings];
|
||||
syncTags = [[settings objectForKey: @"Calendar"]
|
||||
objectForKey: @"FolderSyncTags"];
|
||||
values = [syncTags allValues];
|
||||
if (![values containsObject: newSyncTag])
|
||||
[self setFolderPropertyValue: newSyncTag
|
||||
inCategory: @"FolderSyncTags"];
|
||||
}
|
||||
else
|
||||
[self setFolderPropertyValue: nil
|
||||
inCategory: @"FolderSyncTags"];
|
||||
}
|
||||
|
||||
- (BOOL) synchronizeCalendar
|
||||
{
|
||||
NSNumber *synchronize;
|
||||
|
||||
synchronize = [self folderPropertyValueInCategory: @"FolderSynchronize"];
|
||||
|
||||
return [synchronize boolValue];
|
||||
}
|
||||
|
||||
- (void) setSynchronizeCalendar: (BOOL) new
|
||||
{
|
||||
NSNumber *synchronize;
|
||||
|
||||
if (new)
|
||||
synchronize = [NSNumber numberWithBool: YES];
|
||||
else
|
||||
synchronize = nil;
|
||||
|
||||
[self setFolderPropertyValue: synchronize
|
||||
inCategory: @"FolderSynchronize"];
|
||||
}
|
||||
|
||||
//
|
||||
// If the user is the owner of the calendar, by default we include the freebusy information.
|
||||
//
|
||||
@@ -2819,70 +2869,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
return [url stringByAppendingString:_uid];
|
||||
}
|
||||
|
||||
- (NSString *) _baseCalDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!baseCalDAVURL)
|
||||
{
|
||||
davURL = [[self realDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
baseCalDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
baseCalDAVURL = davURL;
|
||||
[baseCalDAVURL retain];
|
||||
}
|
||||
|
||||
return baseCalDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) _basePublicCalDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!basePublicCalDAVURL)
|
||||
{
|
||||
davURL = [[self publicDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
basePublicCalDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
basePublicCalDAVURL = davURL;
|
||||
[basePublicCalDAVURL retain];
|
||||
}
|
||||
|
||||
return basePublicCalDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) calDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _baseCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) webDavICSURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@.ics", [self _baseCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) webDavXMLURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@.xml", [self _baseCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) publicCalDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _basePublicCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) publicWebDavICSURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@.ics", [self _basePublicCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) publicWebDavXMLURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@.xml", [self _basePublicCalDAVURL]];
|
||||
}
|
||||
|
||||
/* folder management */
|
||||
- (BOOL) create
|
||||
{
|
||||
@@ -2928,6 +2914,12 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
|
||||
refArray = [moduleSettings objectForKey: @"InactiveFolders"];
|
||||
[refArray removeObject: nameInContainer];
|
||||
|
||||
refDict = [moduleSettings objectForKey: @"FolderSyncTags"];
|
||||
[refDict removeObjectForKey: reference];
|
||||
|
||||
refDict = [moduleSettings objectForKey: @"FolderSynchronize"];
|
||||
[refDict removeObjectForKey: reference];
|
||||
|
||||
[super removeFolderSettings: moduleSettings
|
||||
withReference: reference];
|
||||
@@ -2988,7 +2980,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
|
||||
theUser = [SOGoUser userWithLogin: theUID];
|
||||
aParent = [theUser calendarsFolderInContext: context];
|
||||
|
||||
|
||||
if ([aParent isKindOfClass: [NSException class]])
|
||||
return nil;
|
||||
|
||||
aFolders = [aParent subFolders];
|
||||
e = [aFolders objectEnumerator];
|
||||
while ((aFolder = [e nextObject]))
|
||||
@@ -3140,11 +3135,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
return (![inactiveFolders containsObject: nameInContainer]);
|
||||
}
|
||||
|
||||
- (BOOL) isWebCalendar
|
||||
{
|
||||
return ([self isKindOfClass: [SOGoWebAppointmentFolder class]]);
|
||||
}
|
||||
|
||||
- (NSString *) importComponent: (iCalEntityObject *) event
|
||||
timezone: (iCalTimeZone *) timezone
|
||||
{
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
#import <NGCards/iCalEventChanges.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalRecurrenceCalculator.h>
|
||||
#import <NGCards/iCalToDo.h>
|
||||
#import <NGCards/NSCalendarDate+NGCards.h>
|
||||
#import <SaxObjC/XMLNamespaces.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalEntityObject+SOGo.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -26,7 +26,6 @@
|
||||
@class iCalAlarm;
|
||||
@class NSMutableDictionary;
|
||||
@class SOGoUser;
|
||||
@class WOContext;
|
||||
|
||||
extern NSCalendarDate *iCalDistantFuture;
|
||||
extern NSNumber *iCalDistantFutureNumber;
|
||||
@@ -35,15 +34,12 @@ extern NSNumber *iCalDistantFutureNumber;
|
||||
|
||||
+ (void) initializeSOGoExtensions;
|
||||
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context;
|
||||
- (void) setAttributes: (NSDictionary *) data
|
||||
inContext: (WOContext *) context;
|
||||
|
||||
- (BOOL) userIsAttendee: (SOGoUser *) user;
|
||||
- (BOOL) userIsOrganizer: (SOGoUser *) user;
|
||||
|
||||
- (iCalPerson *) userAsAttendee: (SOGoUser *) user;
|
||||
|
||||
- (NSArray *) attendeeUIDs;
|
||||
- (BOOL) isStillRelevant;
|
||||
|
||||
- (id) itipEntryWithMethod: (NSString *) method;
|
||||
@@ -51,13 +47,7 @@ extern NSNumber *iCalDistantFutureNumber;
|
||||
- (NSArray *) attendeesWithoutUser: (SOGoUser *) user;
|
||||
|
||||
- (int) priorityNumber;
|
||||
- (NSDictionary *) createdBy;
|
||||
|
||||
- (NSCalendarDate *) dateFromString: (NSString *) dateString
|
||||
inContext: (WOContext *) context;
|
||||
- (void) adjustDate: (NSCalendarDate **) date
|
||||
withTimeString: (NSString *) timeString
|
||||
inContext: (WOContext *) context;
|
||||
- (NSString *) createdBy;
|
||||
|
||||
- (NSNumber *) quickRecordDateAsNumber: (NSCalendarDate *) _date
|
||||
withOffset: (int) offset
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalEntityObject+SOGo.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,24 +20,19 @@
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
#import <NGCards/iCalRepeatableEntityObject.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
|
||||
#import <NGExtensions/NGCalendarDateRange.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "SOGoAppointmentFolder.h"
|
||||
|
||||
@@ -45,13 +40,11 @@
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/SOGoSource.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
|
||||
#import "iCalPerson+SOGo.h"
|
||||
#import "iCalAlarm+SOGo.h"
|
||||
|
||||
#import "iCalCalendar+SOGo.h"
|
||||
#import "iCalEntityObject+SOGo.h"
|
||||
|
||||
@@ -70,336 +63,6 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [UIxAppointmentEditor viewAction]
|
||||
* @see [UIxTaskEditor viewAction]
|
||||
*/
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *elements;
|
||||
NSMutableArray *attendees;
|
||||
NSDictionary *contactData;
|
||||
NSMutableDictionary *data, *organizerData, *attendeeData;
|
||||
NSEnumerator *attendeesList;
|
||||
NSString *uid, *domain, *sentBy;
|
||||
NSObject <SOGoSource> *source;
|
||||
SOGoUserManager *um;
|
||||
iCalPerson *organizer, *currentAttendee;
|
||||
id value;
|
||||
|
||||
um = [SOGoUserManager sharedUserManager];
|
||||
|
||||
data = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
[[self tag] lowercaseString], @"component",
|
||||
[self summary], @"summary",
|
||||
nil];
|
||||
|
||||
value = [self priority];
|
||||
if ([value length]) [data setObject: value forKey: @"priority"];
|
||||
|
||||
value = [self location];
|
||||
if ([value length]) [data setObject: value forKey: @"location"];
|
||||
|
||||
value = [self comment];
|
||||
if ([value length]) [data setObject: value forKey: @"comment"];
|
||||
|
||||
value = [self accessClass];
|
||||
if ([value length]) [data setObject: [value lowercaseString] forKey: @"classification"];
|
||||
|
||||
value = [self status];
|
||||
if ([value length]) [data setObject: [value lowercaseString] forKey: @"status"];
|
||||
|
||||
value = [self createdBy];
|
||||
if (value) [data setObject: value forKey: @"createdBy"];
|
||||
|
||||
// Categories
|
||||
elements = [self categories];
|
||||
if ([elements count])
|
||||
[data setObject: elements forKey: @"categories"];
|
||||
|
||||
// Send appointment notifications when the custom tag is *not* set
|
||||
value = [self firstChildWithTag: @"X-SOGo-Send-Appointment-Notifications"];
|
||||
[data setObject: [NSNumber numberWithBool: (value? 0:1)] forKey: @"sendAppointmentNotifications"];
|
||||
|
||||
// Organizer
|
||||
organizer = [self organizer];
|
||||
if ([[organizer email] length])
|
||||
{
|
||||
organizerData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
[organizer rfc822Email], @"email",
|
||||
[organizer cnWithoutQuotes], @"name",
|
||||
nil];
|
||||
uid = [organizer uid];
|
||||
if ([uid length]) [organizerData setObject: uid forKey: @"uid"];
|
||||
sentBy = [organizer sentBy];
|
||||
if ([sentBy length]) [organizerData setObject: sentBy forKey: @"sentBy"];
|
||||
[data setObject: organizerData forKey: @"organizer"];
|
||||
}
|
||||
|
||||
// Attendees
|
||||
attendees = [NSMutableArray array];
|
||||
attendeesList = [[self attendees] objectEnumerator];
|
||||
while ((currentAttendee = [attendeesList nextObject]))
|
||||
{
|
||||
attendeeData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
[currentAttendee rfc822Email], @"email",
|
||||
[currentAttendee cnWithoutQuotes], @"name",
|
||||
nil];
|
||||
if ((uid = [currentAttendee uid]))
|
||||
{
|
||||
[attendeeData setObject: uid forKey: @"uid"];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Search for attendee in global contacts sources that are associated with a MS Exchange server
|
||||
domain = [[context activeUser] domain];
|
||||
elements = [um fetchContactsMatching: [currentAttendee rfc822Email] inDomain: domain];
|
||||
if ([elements count] == 1)
|
||||
{
|
||||
contactData = [elements lastObject];
|
||||
source = [contactData objectForKey: @"source"];
|
||||
if ([source conformsToProtocol: @protocol (SOGoDNSource)] &&
|
||||
[[(NSObject <SOGoDNSource>*) source MSExchangeHostname] length])
|
||||
{
|
||||
uid = [NSString stringWithFormat: @"%@:%@", [[context activeUser] login],
|
||||
[contactData valueForKey: @"c_uid"]];
|
||||
[attendeeData setObject: uid forKey: @"uid"];
|
||||
}
|
||||
}
|
||||
}
|
||||
[attendeeData setObject: [[currentAttendee partStat] lowercaseString] forKey: @"status"];
|
||||
[attendeeData setObject: [[currentAttendee role] lowercaseString] forKey: @"role"];
|
||||
if ([[currentAttendee delegatedTo] length])
|
||||
[attendeeData setObject: [[currentAttendee delegatedTo] rfc822Email] forKey: @"delegatedTo"];
|
||||
if ([[currentAttendee delegatedFrom] length])
|
||||
[attendeeData setObject: [[currentAttendee delegatedFrom] rfc822Email] forKey: @"delegatedFrom"];
|
||||
|
||||
[attendees addObject: attendeeData];
|
||||
}
|
||||
if ([attendees count])
|
||||
[data setObject: attendees forKey: @"attendees"];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// From [UIxDatePicker takeValuesFromRequest:inContext:]
|
||||
- (NSCalendarDate *) dateFromString: (NSString *) dateString
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
NSInteger dateTZOffset, userTZOffset;
|
||||
NSTimeZone *systemTZ, *userTZ;
|
||||
SOGoUserDefaults *ud;
|
||||
NSCalendarDate *date;
|
||||
|
||||
date = [NSCalendarDate dateWithString: dateString
|
||||
calendarFormat: @"%Y-%m-%d"];
|
||||
if (!date)
|
||||
[self warnWithFormat: @"Could not parse dateString: '%@'", dateString];
|
||||
|
||||
// We must adjust the date timezone because "dateWithString:..." uses the
|
||||
// system timezone, which can be different from the user's. */
|
||||
ud = [[context activeUser] userDefaults];
|
||||
systemTZ = [date timeZone];
|
||||
dateTZOffset = [systemTZ secondsFromGMTForDate: date];
|
||||
userTZ = [ud timeZone];
|
||||
userTZOffset = [userTZ secondsFromGMTForDate: date];
|
||||
if (dateTZOffset != userTZOffset)
|
||||
date = [date dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: (dateTZOffset - userTZOffset)];
|
||||
[date setTimeZone: userTZ];
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
// From [UIxTimeDatePicker takeValuesFromRequest:inContext:]
|
||||
- (void) adjustDate: (NSCalendarDate **) date
|
||||
withTimeString: (NSString *) timeString
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
unsigned _year, _month, _day, _hour, _minute;
|
||||
SOGoUserDefaults *ud;
|
||||
NSArray *_time;
|
||||
|
||||
_year = [*date yearOfCommonEra];
|
||||
_month = [*date monthOfYear];
|
||||
_day = [*date dayOfMonth];
|
||||
_time = [timeString componentsSeparatedByString: @":"];
|
||||
_hour = [[_time objectAtIndex: 0] intValue];
|
||||
_minute = [[_time objectAtIndex: 1] intValue];
|
||||
|
||||
ud = [[context activeUser] userDefaults];
|
||||
*date = [NSCalendarDate dateWithYear: _year month: _month day: _day
|
||||
hour: _hour minute: _minute second: 0
|
||||
timeZone: [ud timeZone]];
|
||||
}
|
||||
|
||||
- (void) _setAttendees: (NSArray *) attendees
|
||||
{
|
||||
NSMutableArray *newAttendees;
|
||||
NSUInteger count, max;
|
||||
NSString *currentEmail;
|
||||
iCalPerson *currentAttendee;
|
||||
NSString *role, *partstat;
|
||||
NSDictionary *currentData;
|
||||
|
||||
if (attendees)
|
||||
{
|
||||
newAttendees = [NSMutableArray array];
|
||||
max = [attendees count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentData = [attendees objectAtIndex: count];
|
||||
currentEmail = [currentData objectForKey: @"email"];
|
||||
if ([currentEmail length] > 0)
|
||||
{
|
||||
role = [[currentData objectForKey: @"role"] uppercaseString];
|
||||
if (!role)
|
||||
role = @"REQ-PARTICIPANT";
|
||||
if ([role isEqualToString: @"NON-PARTICIPANT"])
|
||||
partstat = @"";
|
||||
else
|
||||
{
|
||||
partstat = [[currentData objectForKey: @"partstat"] uppercaseString];
|
||||
if (!partstat)
|
||||
partstat = @"NEEDS-ACTION";
|
||||
}
|
||||
currentAttendee = [self findAttendeeWithEmail: currentEmail];
|
||||
if (!currentAttendee)
|
||||
{
|
||||
currentAttendee = [iCalPerson elementWithTag: @"attendee"];
|
||||
[currentAttendee setCn: [currentData objectForKey: @"name"]];
|
||||
[currentAttendee setEmail: currentEmail];
|
||||
}
|
||||
[currentAttendee
|
||||
setRsvp: ([role isEqualToString: @"NON-PARTICIPANT"]
|
||||
? @"FALSE"
|
||||
: @"TRUE")];
|
||||
[currentAttendee setRole: role];
|
||||
[currentAttendee setPartStat: partstat];
|
||||
[newAttendees addObject: currentAttendee];
|
||||
}
|
||||
}
|
||||
[self setAttendees: newAttendees];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _appendAttendeesToEmailAlarm: (iCalAlarm *) alarm
|
||||
{
|
||||
NSArray *attendees;
|
||||
NSMutableArray *aAttendees;
|
||||
int count, max;
|
||||
iCalPerson *currentAttendee, *aAttendee;
|
||||
|
||||
attendees = [self attendees];
|
||||
max = [attendees count];
|
||||
aAttendees = [NSMutableArray arrayWithCapacity: max];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentAttendee = [attendees objectAtIndex: count];
|
||||
aAttendee = [iCalPerson elementWithTag: @"attendee"];
|
||||
[aAttendee setCn: [currentAttendee cn]];
|
||||
[aAttendee setEmail: [currentAttendee rfc822Email]];
|
||||
[aAttendees addObject: aAttendee];
|
||||
}
|
||||
[alarm setAttendees: aAttendees];
|
||||
}
|
||||
|
||||
- (void) _setAlarm: (NSDictionary *) alarm
|
||||
forOwner: (NSString *) owner
|
||||
{
|
||||
iCalAlarm *anAlarm;
|
||||
NSString *reminderAction, *reminderUnit, *reminderQuantity, *reminderReference, *reminderRelation;
|
||||
BOOL reminderEmailAttendees, reminderEmailOrganizer;
|
||||
|
||||
reminderAction = [alarm objectForKey: @"action"];
|
||||
reminderUnit = [alarm objectForKey: @"unit"];
|
||||
reminderQuantity = [alarm objectForKey: @"quantity"];
|
||||
reminderReference = [alarm objectForKey: @"reference"];
|
||||
reminderRelation = [alarm objectForKey: @"relation"];
|
||||
reminderEmailAttendees = [[alarm objectForKey: @"attendees"] boolValue];
|
||||
reminderEmailOrganizer = [[alarm objectForKey: @"organizer"] boolValue];
|
||||
anAlarm = [iCalAlarm alarmForEvent: self
|
||||
owner: owner
|
||||
action: reminderAction
|
||||
unit: reminderUnit
|
||||
quantity: reminderQuantity
|
||||
reference: reminderReference
|
||||
reminderRelation: reminderRelation
|
||||
emailAttendees: reminderEmailAttendees
|
||||
emailOrganizer: reminderEmailOrganizer];
|
||||
|
||||
// If there was an unsupported alarm defined in the event, it will be deleted.
|
||||
[self removeAllAlarms];
|
||||
if (anAlarm)
|
||||
{
|
||||
[self addToAlarms: anAlarm];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [UIxAppointmentEditor saveAction]
|
||||
*/
|
||||
- (void) setAttributes: (NSDictionary *) data
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
NSString *owner;
|
||||
id o, sendAppointmentNotifications;
|
||||
|
||||
o = [data objectForKey: @"summary"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
[self setSummary: o];
|
||||
|
||||
o = [data objectForKey: @"priority"];
|
||||
if ([o isKindOfClass: [NSNumber class]])
|
||||
[self setPriority: [NSString stringWithFormat: @"%d", [o intValue]]];
|
||||
|
||||
o = [data objectForKey: @"location"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
[self setLocation: o];
|
||||
|
||||
o = [data objectForKey: @"comment"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
[self setComment: [o stringByReplacingString: @"\r\n" withString: @"\n"]];
|
||||
|
||||
o = [data objectForKey: @"classification"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
[self setAccessClass: [o uppercaseString]];
|
||||
|
||||
o = [data objectForKey: @"status"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
[self setStatus: [o uppercaseString]];
|
||||
|
||||
o = [data objectForKey: @"categories"];
|
||||
if ([o isKindOfClass: [NSArray class]])
|
||||
[self setCategories: o];
|
||||
|
||||
o = [data objectForKey: @"sendAppointmentNotifications"];
|
||||
if ([o isKindOfClass: [NSNumber class]])
|
||||
{
|
||||
sendAppointmentNotifications = [self firstChildWithTag: @"X-SOGo-Send-Appointment-Notifications"];
|
||||
if (!sendAppointmentNotifications && ![o boolValue])
|
||||
[self addChild: [CardElement simpleElementWithTag: @"X-SOGo-Send-Appointment-Notifications" value: @"NO"]];
|
||||
else if (sendAppointmentNotifications && [o boolValue])
|
||||
[self removeChild: sendAppointmentNotifications];
|
||||
}
|
||||
|
||||
o = [data objectForKey: @"attendees"];
|
||||
if ([o isKindOfClass: [NSArray class]])
|
||||
[self _setAttendees: o];
|
||||
|
||||
o = [data objectForKey: @"alarm"];
|
||||
if ([o isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
owner = [data objectForKey: @"owner"];
|
||||
[self _setAlarm: o forOwner: owner];
|
||||
}
|
||||
|
||||
// Other attributes depend on the client object and therefore are set in [UIxComponentEditor setAttributes:]:
|
||||
// - organizer & "created-by"
|
||||
// - timestamps (creation/modification)
|
||||
}
|
||||
|
||||
- (BOOL) userIsAttendee: (SOGoUser *) user
|
||||
{
|
||||
NSEnumerator *attendees;
|
||||
@@ -450,11 +113,10 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
|
||||
if ([mail length])
|
||||
return [user hasEmail: mail];
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*
|
||||
- (NSArray *) attendeeUIDs
|
||||
{
|
||||
NSEnumerator *attendees;
|
||||
@@ -474,7 +136,6 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
|
||||
return uids;
|
||||
}
|
||||
*/
|
||||
|
||||
#warning this method should be implemented in a category of iCalToDo
|
||||
- (BOOL) isStillRelevant
|
||||
@@ -491,11 +152,11 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
|
||||
NSArray *events;
|
||||
int i, count;
|
||||
|
||||
|
||||
newCalendar = [parent mutableCopy];
|
||||
[newCalendar autorelease];
|
||||
[newCalendar setMethod: method];
|
||||
|
||||
|
||||
events = [newCalendar childrenWithTag: tag];
|
||||
count = [events count];
|
||||
if (count > 1)
|
||||
@@ -589,39 +250,19 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
return priorityNumber;
|
||||
}
|
||||
|
||||
- (NSDictionary *) createdBy
|
||||
- (NSString *) createdBy
|
||||
{
|
||||
NSString *created_by, *created_by_name, *login;
|
||||
NSDictionary *createdByData;
|
||||
SOGoUser *user;
|
||||
NSString *created_by;
|
||||
|
||||
created_by_name = nil;
|
||||
createdByData = nil;
|
||||
created_by = [[self firstChildWithTag: @"X-SOGo-Component-Created-By"] flattenedValuesForKey: @""];
|
||||
|
||||
|
||||
// We consider "SENT-BY" in case our custom header isn't found
|
||||
if (![created_by length])
|
||||
{
|
||||
// We consider "SENT-BY" in case our custom header isn't found
|
||||
created_by = [[self organizer] sentBy];
|
||||
}
|
||||
|
||||
if ([created_by length])
|
||||
{
|
||||
login = [[SOGoUserManager sharedUserManager] getUIDForEmail: created_by];
|
||||
if (login)
|
||||
{
|
||||
user = [SOGoUser userWithLogin: login];
|
||||
if (user)
|
||||
created_by_name = [user cn];
|
||||
}
|
||||
|
||||
createdByData = [NSDictionary dictionaryWithObjectsAndKeys: created_by, @"email",
|
||||
created_by_name, @"name",
|
||||
nil];
|
||||
}
|
||||
|
||||
return createdByData;
|
||||
;
|
||||
return created_by;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -708,20 +349,20 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
NSCalendarDate *start, *end;
|
||||
NGCalendarDateRange *range;
|
||||
NSMutableArray *alarms;
|
||||
|
||||
|
||||
alarms = [NSMutableArray array];
|
||||
start = [NSCalendarDate date];
|
||||
end = [start addYear:1 month:0 day:0 hour:0 minute:0 second:0];
|
||||
range = [NGCalendarDateRange calendarDateRangeWithStartDate: start
|
||||
endDate: end];
|
||||
|
||||
|
||||
// Always check if container is defined. If not, that means this method
|
||||
// call was reentrant.
|
||||
if (theContainer)
|
||||
{
|
||||
NSTimeInterval now;
|
||||
int i, v, delta, c_startdate, c_nextalarm;
|
||||
|
||||
|
||||
//
|
||||
// Here is the logic:
|
||||
//
|
||||
@@ -736,12 +377,12 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
// If we don't have a match, we pick the event for which its trigger is the closest to the c_nextalarm.
|
||||
//
|
||||
nextAlarmDate = nil;
|
||||
|
||||
|
||||
[theContainer flattenCycleRecord: (id)row
|
||||
forRange: range
|
||||
intoArray: alarms
|
||||
withCalendar: [self parent]];
|
||||
|
||||
|
||||
// We pickup the closest one from now. We remove the actual reminder (ie., 15 mins before - plus 1 minute for roundups)
|
||||
// so we don't pickup the same alarm over and over. This could happen if our alarm popups and the user clicks on "Cancel".
|
||||
// In case of a repetitive event, we want to pickup the next one (next day for example), and not the one that has just
|
||||
@@ -753,11 +394,11 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
c_startdate = [[[alarms objectAtIndex: i] objectForKey: @"c_startdate"] intValue];
|
||||
c_nextalarm = [[[alarms objectAtIndex: i] objectForKey: @"c_nextalarm"] intValue];
|
||||
delta = (c_startdate - now - (c_startdate - c_nextalarm)) - 60;
|
||||
|
||||
|
||||
// If value is not initialized, we grab it right away
|
||||
if (!v && delta > 0)
|
||||
v = delta;
|
||||
|
||||
|
||||
// If we found a smaller delta than before, use it.
|
||||
if (v > 0 && delta > 0 && delta <= v)
|
||||
{
|
||||
@@ -768,10 +409,10 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
o = [[self parent] eventWithRecurrenceID: [[alarms objectAtIndex: i] objectForKey: @"c_recurrence_id"]];
|
||||
else
|
||||
o = [[self parent] todoWithRecurrenceID: [[alarms objectAtIndex: i] objectForKey: @"c_recurrence_id"]];
|
||||
|
||||
|
||||
if (!o)
|
||||
o = self;
|
||||
|
||||
|
||||
if ([[o alarms] count])
|
||||
{
|
||||
anAlarm = [self firstDisplayOrAudioAlarm];
|
||||
@@ -781,7 +422,7 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
if (!webstatus
|
||||
|| ([webstatus caseInsensitiveCompare: @"TRIGGERED"]
|
||||
!= NSOrderedSame))
|
||||
|
||||
|
||||
v = delta;
|
||||
nextAlarmDate = [NSDate dateWithTimeIntervalSince1970: [[[alarms objectAtIndex: i] objectForKey: @"c_nextalarm"] intValue]];
|
||||
}
|
||||
@@ -795,7 +436,7 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
} // if (theContainer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ([nextAlarmDate isNotNull])
|
||||
[row setObject: [NSNumber numberWithInt: [nextAlarmDate timeIntervalSince1970]]
|
||||
forKey: @"c_nextalarm"];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalEvent+SOGo.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,7 +28,7 @@
|
||||
@interface iCalEvent (SOGoExtensions)
|
||||
|
||||
- (BOOL) isStillRelevant;
|
||||
- (NSTimeInterval) occurenceInterval;
|
||||
- (unsigned int) occurenceInterval;
|
||||
- (void) updateRecurrenceRulesUntilDate: (NSCalendarDate *) previousEndDate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalEvent+SOGo.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -22,14 +22,12 @@
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGExtensions/NGCalendarDateRange.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
@@ -38,12 +36,6 @@
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
|
||||
#import <SoObjects/SOGo/SOGoUser.h>
|
||||
#import <SoObjects/SOGo/SOGoUserDefaults.h>
|
||||
#import <SoObjects/SOGo/WOContext+SOGo.h>
|
||||
|
||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||
|
||||
#import "SOGoAppointmentFolder.h"
|
||||
#import "iCalRepeatableEntityObject+SOGo.h"
|
||||
|
||||
@@ -55,7 +47,7 @@
|
||||
{
|
||||
NSCalendarDate *now, *lastRecurrence;
|
||||
BOOL isStillRelevent;
|
||||
|
||||
|
||||
now = [NSCalendarDate calendarDate];
|
||||
|
||||
if ([self isRecurrent])
|
||||
@@ -65,7 +57,7 @@
|
||||
}
|
||||
else
|
||||
isStillRelevent = ([[self endDate] earlierDate: now] == now);
|
||||
|
||||
|
||||
return isStillRelevent;
|
||||
}
|
||||
|
||||
@@ -122,8 +114,8 @@
|
||||
row = [NSMutableDictionary dictionaryWithCapacity:8];
|
||||
|
||||
[row setObject: @"vevent" forKey: @"c_component"];
|
||||
|
||||
if ([uid isNotNull])
|
||||
|
||||
if ([uid isNotNull])
|
||||
[row setObject:uid forKey: @"c_uid"];
|
||||
else
|
||||
[self logWithFormat: @"WARNING: could not extract a uid from event!"];
|
||||
@@ -176,11 +168,11 @@
|
||||
forAllDay: isAllDay]
|
||||
forKey: @"c_enddate"];
|
||||
}
|
||||
|
||||
|
||||
if ([self isRecurrent])
|
||||
{
|
||||
NSCalendarDate *date;
|
||||
|
||||
|
||||
date = [self lastPossibleRecurrenceStartDate];
|
||||
if (!date)
|
||||
{
|
||||
@@ -202,7 +194,7 @@
|
||||
if ([status isNotNull])
|
||||
{
|
||||
int code = 1;
|
||||
|
||||
|
||||
if ([status isEqualToString: @"TENTATIVE"])
|
||||
code = 2;
|
||||
else if ([status isEqualToString: @"CANCELLED"])
|
||||
@@ -222,12 +214,12 @@
|
||||
if (organizer)
|
||||
{
|
||||
NSString *email;
|
||||
|
||||
|
||||
email = [organizer valueForKey: @"rfc822Email"];
|
||||
if (email)
|
||||
[row setObject:email forKey: @"c_orgmail"];
|
||||
}
|
||||
|
||||
|
||||
/* construct partstates */
|
||||
count = [attendees count];
|
||||
partstates = [[NSMutableString alloc] initWithCapacity:count * 2];
|
||||
@@ -235,7 +227,7 @@
|
||||
{
|
||||
iCalPerson *p;
|
||||
iCalPersonPartStat stat;
|
||||
|
||||
|
||||
p = [attendees objectAtIndex:i];
|
||||
stat = [p participationStatus];
|
||||
if (i)
|
||||
@@ -271,7 +263,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift the "until dates" of the recurrence rules of the event
|
||||
* Shift the "until dates" of the recurrence rules of the event
|
||||
* with respect to the previous end date of the event.
|
||||
* @param previousEndDate the previous end date of the event
|
||||
*/
|
||||
@@ -320,137 +312,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [iCalRepeatableEntityObject+SOGo attributes]
|
||||
* @see [iCalEntityObject+SOGo attributes]
|
||||
* @see [UIxAppointmentEditor viewAction]
|
||||
*/
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||
{
|
||||
BOOL isAllDay;
|
||||
NSCalendarDate *eventStartDate, *eventEndDate;
|
||||
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 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"];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [iCalRepeatableEntityObject+SOGo setAttributes:inContext:]
|
||||
* @see [iCalEntityObject+SOGo setAttributes:inContext:]
|
||||
* @see [UIxAppointmentEditor saveAction]
|
||||
*/
|
||||
- (void) setAttributes: (NSDictionary *) data
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
NSCalendarDate *aptStartDate, *aptEndDate, *allDayStartDate;
|
||||
NSTimeZone *timeZone;
|
||||
NSInteger offset, nbrDays;
|
||||
iCalDateTime *startDate;
|
||||
iCalTimeZone *tz;
|
||||
SOGoUserDefaults *ud;
|
||||
BOOL isAllDay;
|
||||
id o;
|
||||
|
||||
[super setAttributes: data inContext: context];
|
||||
|
||||
aptStartDate = aptEndDate = nil;
|
||||
|
||||
// Handle start/end dates
|
||||
o = [data objectForKey: @"startDate"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
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"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
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"];
|
||||
if ([o isKindOfClass: [NSNumber class]])
|
||||
[self setTransparency: ([o boolValue]? @"TRANSPARENT" : @"OPAQUE")];
|
||||
|
||||
isAllDay = [[data objectForKey: @"isAllDay"] boolValue];
|
||||
|
||||
if (aptStartDate && aptEndDate)
|
||||
{
|
||||
if (isAllDay)
|
||||
{
|
||||
nbrDays = ((float) abs ([aptEndDate timeIntervalSinceDate: aptStartDate]) / 86400) + 1;
|
||||
// Convert all-day start date to GMT (floating date)
|
||||
ud = [[context activeUser] userDefaults];
|
||||
timeZone = [ud timeZone];
|
||||
offset = [timeZone secondsFromGMTForDate: aptStartDate];
|
||||
allDayStartDate = [aptStartDate dateByAddingYears: 0 months: 0 days: 0
|
||||
hours: 0 minutes: 0
|
||||
seconds: offset];
|
||||
[self setAllDayWithStartDate: allDayStartDate
|
||||
duration: nbrDays];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setStartDate: aptStartDate];
|
||||
[self setEndDate: aptEndDate];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAllDay)
|
||||
{
|
||||
// Make sure there's a vTimeZone associated to the event unless it
|
||||
// is an all-day event.
|
||||
startDate = (iCalDateTime *)[self uniqueChildWithTag: @"dtstart"];
|
||||
if (![startDate timeZone])
|
||||
{
|
||||
ud = [[context activeUser] userDefaults];
|
||||
tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]];
|
||||
if ([[self parent] addTimeZone: tz])
|
||||
{
|
||||
[startDate setTimeZone: tz];
|
||||
[(iCalDateTime *)[self uniqueChildWithTag: @"dtend"] setTimeZone: tz];
|
||||
}
|
||||
}
|
||||
}
|
||||
else // if (![[self clientObject] isNew])
|
||||
{
|
||||
// Remove the vTimeZone when dealing with an all-day event.
|
||||
startDate = (iCalDateTime *)[self uniqueChildWithTag: @"dtstart"];
|
||||
tz = [startDate timeZone];
|
||||
if (tz)
|
||||
{
|
||||
[startDate setTimeZone: nil];
|
||||
[(iCalDateTime *)[self uniqueChildWithTag: @"dtend"] setTimeZone: nil];
|
||||
[[self parent] removeChild: tz];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalRepeatableEntityObject+SOGo.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalRepeatableEntityObject+SOGo.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,10 +25,8 @@
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGCards/iCalByDayMask.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalRecurrenceRule.h>
|
||||
#import <NGCards/iCalRecurrenceCalculator.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
@@ -37,12 +35,6 @@
|
||||
|
||||
#import <NGExtensions/NGCalendarDateRange.h>
|
||||
|
||||
#import <SoObjects/SOGo/SOGoUser.h>
|
||||
#import <SoObjects/SOGo/SOGoUserDefaults.h>
|
||||
#import <SoObjects/SOGo/WOContext+SOGo.h>
|
||||
|
||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||
|
||||
#import "iCalRepeatableEntityObject+SOGo.h"
|
||||
|
||||
@implementation iCalRepeatableEntityObject (SOGoExtensions)
|
||||
@@ -77,152 +69,6 @@
|
||||
return ma;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [iCalEntityObject+SOGo attributes]
|
||||
* @see [iCalEvent+SOGo attributes]
|
||||
* @see [UIxAppointmentEditor viewAction]
|
||||
*/
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *rules;
|
||||
NSCalendarDate *untilDate;
|
||||
NSMutableDictionary *data, *repeat;
|
||||
NSString *frequency;
|
||||
NSTimeZone *timeZone;
|
||||
SOGoUserDefaults *ud;
|
||||
iCalRecurrenceRule *rule;
|
||||
|
||||
data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]];
|
||||
|
||||
rules = [self recurrenceRules];
|
||||
if ([rules count] > 0)
|
||||
{
|
||||
rule = [rules objectAtIndex: 0];
|
||||
frequency = [rule frequencyForValue: [rule frequency]];
|
||||
|
||||
repeat = [NSMutableDictionary dictionary];
|
||||
[repeat setObject: [frequency lowercaseString] forKey: @"frequency"];
|
||||
[repeat setObject: [NSNumber numberWithInt: [rule repeatInterval]] forKey: @"interval"];
|
||||
if ([rule repeatCount])
|
||||
[repeat setObject: [NSNumber numberWithInt: [rule repeatCount]] forKey: @"count"];
|
||||
if ((untilDate = [rule untilDate]))
|
||||
{
|
||||
ud = [[context activeUser] userDefaults];
|
||||
timeZone = [ud timeZone];
|
||||
[untilDate setTimeZone: timeZone];
|
||||
[repeat setObject: [untilDate iso8601DateString] forKey: @"until"];
|
||||
}
|
||||
if ([[rule byDay] length])
|
||||
[repeat setObject: [[rule byDayMask] asRuleArray] forKey: @"days"];
|
||||
if ([[rule byMonthDay] count])
|
||||
[repeat setObject: [rule byMonthDay] forKey: @"monthdays"];
|
||||
if ([[rule byMonth] count])
|
||||
[repeat setObject: [rule byMonth] forKey: @"months"];
|
||||
[data setObject: repeat forKey: @"repeat"];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [iCalEntityObject+SOGo setAttributes:inContext:]
|
||||
* @see [UIxAppointmentEditor saveAction]
|
||||
*/
|
||||
- (void) setAttributes: (NSDictionary *) data
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
iCalRecurrenceRule *rule;
|
||||
iCalRecurrenceFrequency frequency;
|
||||
NSCalendarDate *date;
|
||||
SOGoUserDefaults *ud;
|
||||
id repeat, o;
|
||||
|
||||
[super setAttributes: data inContext: context];
|
||||
|
||||
if ([self recurrenceId])
|
||||
// Occurrence of a recurrent object can't have a recurrence rule
|
||||
return;
|
||||
|
||||
repeat = [data objectForKey: @"repeat"];
|
||||
if ([repeat isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
rule = [iCalRecurrenceRule new];
|
||||
[rule setInterval: @"1"];
|
||||
|
||||
frequency = NSNotFound;
|
||||
o = [repeat objectForKey: @"frequency"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
{
|
||||
frequency = [rule valueForFrequency: o];
|
||||
if ((NSUInteger) frequency == NSNotFound)
|
||||
{
|
||||
if ([o caseInsensitiveCompare: @"BI-WEEKLY"] == NSOrderedSame)
|
||||
{
|
||||
frequency = iCalRecurrenceFrequenceWeekly;
|
||||
[rule setInterval: @"2"];
|
||||
}
|
||||
else if ([o caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame)
|
||||
{
|
||||
frequency = iCalRecurrenceFrequenceDaily;
|
||||
[rule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
o = [repeat objectForKey: @"interval"];
|
||||
if ([o isKindOfClass: [NSNumber class]])
|
||||
[rule setInterval: [NSString stringWithFormat: @"%i", [o intValue]]];
|
||||
|
||||
o = [repeat objectForKey: @"count"];
|
||||
if ([o isKindOfClass: [NSNumber class]])
|
||||
[rule setRepeatCount: [o intValue]];
|
||||
|
||||
o = [repeat objectForKey: @"until"];
|
||||
if ([o isKindOfClass: [NSString class]])
|
||||
{
|
||||
date = [NSCalendarDate dateWithString: o
|
||||
calendarFormat: @"%Y-%m-%d"];
|
||||
if (date)
|
||||
{
|
||||
// Adjust timezone
|
||||
ud = [[context activeUser] userDefaults];
|
||||
date = [NSCalendarDate dateWithYear: [date yearOfCommonEra]
|
||||
month: [date monthOfYear]
|
||||
day: [date dayOfMonth]
|
||||
hour: 0 minute: 0 second: 0
|
||||
timeZone: [ud timeZone]];
|
||||
[rule setUntilDate: date];
|
||||
}
|
||||
}
|
||||
|
||||
o = [repeat objectForKey: @"days"];
|
||||
if ([o isKindOfClass: [NSArray class]])
|
||||
[rule setByDayMask: [iCalByDayMask byDayMaskWithDaysAndOccurrences: o]];
|
||||
|
||||
o = [repeat objectForKey: @"monthdays"];
|
||||
if ([o isKindOfClass: [NSArray class]])
|
||||
[rule setValues: o atIndex: 0 forKey: @"bymonthday"];
|
||||
|
||||
o = [repeat objectForKey: @"months"];
|
||||
if ([o isKindOfClass: [NSArray class]])
|
||||
[rule setValues: o atIndex: 0 forKey: @"bymonth"];
|
||||
}
|
||||
|
||||
if ((NSUInteger) frequency != NSNotFound)
|
||||
{
|
||||
[rule setFrequency: frequency];
|
||||
[self setRecurrenceRules: [NSArray arrayWithObject: rule]];
|
||||
}
|
||||
|
||||
[rule release];
|
||||
}
|
||||
}
|
||||
else if ([self hasRecurrenceRules])
|
||||
{
|
||||
[self removeAllRecurrenceRules];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *) cycleInfo
|
||||
{
|
||||
NSArray *rules;
|
||||
@@ -237,7 +83,7 @@
|
||||
rules = [self _indexedRules: [self recurrenceRules]];
|
||||
if (rules)
|
||||
[cycleInfo setObject: rules forKey: @"rules"];
|
||||
|
||||
|
||||
rules = [self _indexedRules: [self exceptionRules]];
|
||||
if (rules)
|
||||
[cycleInfo setObject: rules forKey: @"exRules"];
|
||||
@@ -276,7 +122,7 @@
|
||||
firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: start
|
||||
endDate: end];
|
||||
}
|
||||
|
||||
|
||||
return firstRange;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,19 +27,10 @@
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
|
||||
#import <SoObjects/SOGo/WOContext+SOGo.h>
|
||||
|
||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
|
||||
#import "iCalRepeatableEntityObject+SOGo.h"
|
||||
|
||||
@@ -47,179 +38,6 @@
|
||||
|
||||
@implementation iCalToDo (SOGoExtensions)
|
||||
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||
{
|
||||
BOOL isAllDayStartDate, isAllDayDueDate;
|
||||
NSCalendarDate *startDate, *dueDate, *completedDate;
|
||||
NSMutableDictionary *data;
|
||||
NSTimeZone *timeZone;
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
ud = [[context activeUser] userDefaults];
|
||||
timeZone = [ud timeZone];
|
||||
|
||||
startDate = [self startDate];
|
||||
isAllDayStartDate = [(iCalDateTime *) [self uniqueChildWithTag: @"dtstart"] isAllDay];
|
||||
if (!isAllDayStartDate)
|
||||
[startDate setTimeZone: timeZone];
|
||||
|
||||
dueDate = [self due];
|
||||
isAllDayDueDate = [(iCalDateTime *) [self uniqueChildWithTag: @"due"] isAllDay];
|
||||
if (!isAllDayDueDate)
|
||||
[dueDate setTimeZone: timeZone];
|
||||
|
||||
completedDate = [self completed];
|
||||
[completedDate setTimeZone: timeZone];
|
||||
|
||||
data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]];
|
||||
|
||||
if (startDate)
|
||||
[data setObject: [startDate iso8601DateString] forKey: @"startDate"];
|
||||
if (dueDate)
|
||||
[data setObject: [dueDate iso8601DateString] forKey: @"dueDate"];
|
||||
if (completedDate)
|
||||
[data setObject: [completedDate iso8601DateString] forKey: @"completedDate"];
|
||||
|
||||
if ([[self percentComplete] length])
|
||||
[data setObject: [NSNumber numberWithInt: [[self percentComplete] intValue]] forKey: @"percentComplete"];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [iCalRepeatableEntityObject+SOGo setAttributes:inContext:]
|
||||
* @see [iCalEntityObject+SOGo setAttributes:inContext:]
|
||||
* @see [UIxAppointmentEditor saveAction]
|
||||
*/
|
||||
- (void) setAttributes: (NSDictionary *) data
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
BOOL isAllDayStartDate, isAllDayDueDate;
|
||||
NSCalendarDate *startDate, *dueDate, *completedDate;
|
||||
NSInteger percent;
|
||||
SOGoUserDefaults *ud;
|
||||
iCalDateTime *todoStartDate, *todoDueDate;
|
||||
iCalTimeZone *tz;
|
||||
id o;
|
||||
|
||||
[super setAttributes: data inContext: context];
|
||||
|
||||
startDate = dueDate = completedDate = nil;
|
||||
|
||||
// Handle start date
|
||||
isAllDayStartDate = YES;
|
||||
o = [data objectForKey: @"startDate"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
startDate = [self dateFromString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"startTime"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
{
|
||||
isAllDayStartDate = NO;
|
||||
[self adjustDate: &startDate withTimeString: o inContext: context];
|
||||
}
|
||||
|
||||
if (startDate)
|
||||
[self setStartDate: startDate];
|
||||
else
|
||||
{
|
||||
[self setStartDate: nil];
|
||||
[self removeAllAlarms];
|
||||
}
|
||||
|
||||
// Handle due date
|
||||
isAllDayDueDate = YES;
|
||||
o = [data objectForKey: @"dueDate"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
dueDate = [self dateFromString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"dueTime"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
{
|
||||
isAllDayDueDate = NO;
|
||||
[self adjustDate: &dueDate withTimeString: o inContext: context];
|
||||
}
|
||||
|
||||
if (dueDate)
|
||||
if (isAllDayDueDate)
|
||||
[self setAllDayDue: dueDate];
|
||||
else
|
||||
[self setDue: dueDate];
|
||||
else
|
||||
[self setDue: nil];
|
||||
|
||||
// Handle time zone
|
||||
todoStartDate = (iCalDateTime *)[self uniqueChildWithTag: @"dtstart"];
|
||||
todoDueDate = (iCalDateTime *)[self uniqueChildWithTag: @"due"];
|
||||
tz = [todoStartDate timeZone];
|
||||
if (!tz)
|
||||
tz = [todoDueDate timeZone];
|
||||
|
||||
if (isAllDayStartDate && isAllDayDueDate)
|
||||
{
|
||||
if (tz)
|
||||
[[self parent] removeChild: tz];
|
||||
[todoStartDate setTimeZone: nil];
|
||||
[todoDueDate setTimeZone: nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!tz)
|
||||
{
|
||||
ud = [[context activeUser] userDefaults];
|
||||
tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]];
|
||||
}
|
||||
if (tz)
|
||||
{
|
||||
[[self parent] addTimeZone: tz];
|
||||
if (todoStartDate)
|
||||
{
|
||||
if (isAllDayStartDate)
|
||||
[todoStartDate setTimeZone: nil];
|
||||
else if (![todoStartDate timeZone])
|
||||
[todoStartDate setTimeZone: tz];
|
||||
}
|
||||
if (todoDueDate)
|
||||
{
|
||||
if (isAllDayDueDate)
|
||||
[todoDueDate setTimeZone: nil];
|
||||
else if (![todoDueDate timeZone])
|
||||
[todoDueDate setTimeZone: tz];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle completed date
|
||||
o = [data objectForKey: @"completedDate"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
completedDate = [self dateFromString: o inContext: context];
|
||||
|
||||
o = [data objectForKey: @"completedTime"];
|
||||
if ([o isKindOfClass: [NSString class]] && [o length])
|
||||
[self adjustDate: &completedDate withTimeString: o inContext: context];
|
||||
|
||||
o = [self status];
|
||||
if ([o length])
|
||||
{
|
||||
if ([o isEqualToString: @"COMPLETED"] && completedDate)
|
||||
// As specified in RFC5545, the COMPLETED property must use UTC time
|
||||
[self setCompleted: completedDate];
|
||||
else
|
||||
[self setCompleted: nil];
|
||||
}
|
||||
|
||||
// Percent complete
|
||||
o = [data objectForKey: @"percentComplete"];
|
||||
if ([o isKindOfClass: [NSNumber class]])
|
||||
{
|
||||
percent = [o intValue];
|
||||
if (percent >= 0 && percent <= 100)
|
||||
[self setPercentComplete: [NSString stringWithFormat: @"%i", percent]];
|
||||
}
|
||||
else
|
||||
[self setPercentComplete: @""];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
|
||||
container: (id) theContainer
|
||||
{
|
||||
@@ -235,7 +53,7 @@
|
||||
iCalAccessClass accessClass;
|
||||
|
||||
/* extract values */
|
||||
|
||||
|
||||
startDate = [self startDate];
|
||||
dueDate = [self due];
|
||||
uid = [self uid];
|
||||
@@ -260,7 +78,7 @@
|
||||
|
||||
[row setObject: @"vtodo" forKey: @"c_component"];
|
||||
|
||||
if ([uid isNotNull])
|
||||
if ([uid isNotNull])
|
||||
[row setObject:uid forKey: @"c_uid"];
|
||||
else
|
||||
[self logWithFormat: @"WARNING: could not extract a uid from event!"];
|
||||
@@ -278,7 +96,7 @@
|
||||
[row setObject: title forKey: @"c_title"];
|
||||
if ([location isNotNull]) [row setObject: location forKey: @"c_location"];
|
||||
if ([sequence isNotNull]) [row setObject: sequence forKey: @"c_sequence"];
|
||||
|
||||
|
||||
if ([startDate isNotNull])
|
||||
date = [self quickRecordDateAsNumber: startDate
|
||||
withOffset: 0 forAllDay: NO];
|
||||
@@ -328,12 +146,12 @@
|
||||
if (organizer)
|
||||
{
|
||||
NSString *email;
|
||||
|
||||
|
||||
email = [organizer valueForKey: @"rfc822Email"];
|
||||
if (email)
|
||||
[row setObject:email forKey: @"c_orgmail"];
|
||||
}
|
||||
|
||||
|
||||
/* construct partstates */
|
||||
count = [attendees count];
|
||||
partstates = [[NSMutableString alloc] initWithCapacity:count * 2];
|
||||
@@ -341,7 +159,7 @@
|
||||
{
|
||||
iCalPerson *p;
|
||||
iCalPersonPartStat stat;
|
||||
|
||||
|
||||
p = [attendees objectAtIndex:i];
|
||||
stat = [p participationStatus];
|
||||
if(i != 0)
|
||||
@@ -353,7 +171,7 @@
|
||||
|
||||
/* handle alarms */
|
||||
[self updateNextAlarmDateInRow: row forContainer: theContainer];
|
||||
|
||||
|
||||
categories = [self categories];
|
||||
if ([categories count] > 0)
|
||||
[row setObject: [categories componentsJoinedByString: @","]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* NGVCard+SOGo.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
* Copyright (C) 2009-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -187,59 +187,29 @@ convention:
|
||||
return element;
|
||||
}
|
||||
|
||||
- (void) addElementWithTag: (NSString *) elementTag
|
||||
ofType: (NSString *) type
|
||||
withValue: (id) value
|
||||
{
|
||||
NSArray *allValues;
|
||||
NSEnumerator *list;
|
||||
CardElement *element;
|
||||
|
||||
// value is either an array or a string
|
||||
if ([value isKindOfClass: [NSString class]])
|
||||
allValues = [NSArray arrayWithObject: value];
|
||||
else
|
||||
allValues = value;
|
||||
|
||||
// Add all values as separate elements
|
||||
list = [allValues objectEnumerator];
|
||||
while ((value = [list nextObject]))
|
||||
{
|
||||
element = [CardElement simpleElementWithTag: elementTag
|
||||
singleType: type
|
||||
value: value];
|
||||
[self addChild: element];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _setPhoneValues: (NSDictionary *) ldifRecord
|
||||
{
|
||||
[self addElementWithTag: @"tel"
|
||||
ofType: @"work"
|
||||
withValue: [ldifRecord objectForKey: @"telephonenumber"]];
|
||||
[self addElementWithTag: @"tel"
|
||||
ofType: @"home"
|
||||
withValue: [ldifRecord objectForKey: @"homephone"]];
|
||||
[self addElementWithTag: @"tel"
|
||||
ofType: @"cell"
|
||||
withValue: [ldifRecord objectForKey: @"mobile"]];
|
||||
[self addElementWithTag: @"tel"
|
||||
ofType: @"fax"
|
||||
withValue: [ldifRecord objectForKey: @"facsimiletelephonenumber"]];
|
||||
[self addElementWithTag: @"tel"
|
||||
ofType: @"pager"
|
||||
withValue: [ldifRecord objectForKey: @"pager"]];
|
||||
CardElement *phone;
|
||||
|
||||
phone = [self elementWithTag: @"tel" ofType: @"work"];
|
||||
[phone setSingleValue: [ldifRecord objectForKey: @"telephonenumber"] forKey: @""];
|
||||
phone = [self elementWithTag: @"tel" ofType: @"home"];
|
||||
[phone setSingleValue: [ldifRecord objectForKey: @"homephone"] forKey: @""];
|
||||
phone = [self elementWithTag: @"tel" ofType: @"cell"];
|
||||
[phone setSingleValue: [ldifRecord objectForKey: @"mobile"] forKey: @""];
|
||||
phone = [self elementWithTag: @"tel" ofType: @"fax"];
|
||||
[phone setSingleValue: [ldifRecord objectForKey: @"facsimiletelephonenumber"]
|
||||
forKey: @""];
|
||||
phone = [self elementWithTag: @"tel" ofType: @"pager"];
|
||||
[phone setSingleValue: [ldifRecord objectForKey: @"pager"] forKey: @""];
|
||||
}
|
||||
|
||||
- (void) _setEmails: (NSDictionary *) ldifRecord
|
||||
{
|
||||
CardElement *homeMail;
|
||||
CardElement *mail, *homeMail;
|
||||
|
||||
// Emails from the configured mail fields of the source have already been extracted in
|
||||
// [LDAPSource _fillEmailsOfEntry:intoLDIFRecord:]
|
||||
[self addElementWithTag: @"email"
|
||||
ofType: @"work"
|
||||
withValue: [ldifRecord objectForKey: @"c_emails"]];
|
||||
mail = [self elementWithTag: @"email" ofType: @"work"];
|
||||
[mail setSingleValue: [ldifRecord objectForKey: @"mail"] forKey: @""];
|
||||
homeMail = [self elementWithTag: @"email" ofType: @"home"];
|
||||
[homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""];
|
||||
[[self uniqueChildWithTag: @"x-mozilla-html"]
|
||||
|
||||
@@ -209,26 +209,10 @@ static NSArray *folderListingFields = nil;
|
||||
return qualifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize keys of dictionary representing a contact.
|
||||
* @param contactRecord a dictionary with pairs from the quick table
|
||||
* @see [UIxContactView dataAction]
|
||||
*/
|
||||
- (void) fixupContactRecord: (NSMutableDictionary *) contactRecord
|
||||
{
|
||||
NSString *data;
|
||||
|
||||
// c_categories => categories
|
||||
data = [contactRecord objectForKey: @"c_categories"];
|
||||
if ([data length])
|
||||
[contactRecord setObject: data forKey: @"categories"];
|
||||
|
||||
// c_name => id
|
||||
data = [contactRecord objectForKey: @"c_name"];
|
||||
if ([data length])
|
||||
[contactRecord setObject: data forKey: @"id"];
|
||||
|
||||
// c_cn => fn
|
||||
data = [contactRecord objectForKey: @"c_cn"];
|
||||
if (![data length])
|
||||
{
|
||||
@@ -240,34 +224,17 @@ static NSArray *folderListingFields = nil;
|
||||
else
|
||||
{
|
||||
data = [contactRecord objectForKey: @"c_o"];
|
||||
[contactRecord setObject: data forKey: @"c_cn"];
|
||||
[contactRecord setObject: data forKey: @"c_cn"];
|
||||
}
|
||||
}
|
||||
|
||||
// c_screenname => X-AIM
|
||||
if (![contactRecord objectForKey: @"c_mail"])
|
||||
[contactRecord setObject: @"" forKey: @"c_mail"];
|
||||
if (![contactRecord objectForKey: @"c_screenname"])
|
||||
[contactRecord setObject: @"" forKey: @"c_screenname"];
|
||||
|
||||
// c_mail => emails[]
|
||||
data = [contactRecord objectForKey: @"c_mail"];
|
||||
if ([data length])
|
||||
{
|
||||
NSDictionary *email;
|
||||
email = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil];
|
||||
[contactRecord setObject: [NSArray arrayWithObject: email] forKey: @"emails"];
|
||||
}
|
||||
else
|
||||
[contactRecord setObject: @"" forKey: @"c_mail"];
|
||||
|
||||
// c_telephonenumber => phones
|
||||
data = [contactRecord objectForKey: @"c_telephonenumber"];
|
||||
if ([data length])
|
||||
{
|
||||
NSDictionary *phonenumber;
|
||||
phonenumber = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil];
|
||||
[contactRecord setObject: [NSArray arrayWithObject: phonenumber] forKey: @"phones"];
|
||||
}
|
||||
else
|
||||
if (![contactRecord objectForKey: @"c_o"])
|
||||
[contactRecord setObject: @"" forKey: @"c_o"];
|
||||
if (![contactRecord objectForKey: @"c_telephonenumber"])
|
||||
[contactRecord setObject: @"" forKey: @"c_telephonenumber"];
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
[vcard setProdID: [NSString
|
||||
stringWithFormat: @"-//Inverse inc./SOGo %@//EN",
|
||||
SOGoVersion]];
|
||||
[vcard updateFromLDIFRecord: [self ldifRecord]];
|
||||
[vcard updateFromLDIFRecord: [self simplifiedLDIFRecord]];
|
||||
|
||||
return vcard;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
- (NSDictionary *) simplifiedLDIFRecord;
|
||||
|
||||
- (NSException *) save;
|
||||
- (NSException *) delete;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoContactSourceFolder.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
* Copyright (C) 2006-2013 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -217,11 +217,6 @@
|
||||
return [source removeContactEntryWithID: [ldifEntry nameInContainer]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize keys of dictionary representing a contact.
|
||||
* @param oldRecord a dictionary with pairs from the source folder (LDAP or SQL)
|
||||
* @see [SOGoContactGCSFolder _fixupContactRecord]
|
||||
*/
|
||||
- (NSDictionary *) _flattenedRecord: (NSDictionary *) oldRecord
|
||||
{
|
||||
NSMutableDictionary *newRecord;
|
||||
@@ -231,54 +226,26 @@
|
||||
newRecord = [NSMutableDictionary dictionaryWithCapacity: 8];
|
||||
[newRecord setObject: [oldRecord objectForKey: @"c_uid"]
|
||||
forKey: @"c_uid"];
|
||||
|
||||
// c_name => id
|
||||
[newRecord setObject: [oldRecord objectForKey: @"c_name"]
|
||||
forKey: @"c_name"];
|
||||
[newRecord setObject: [oldRecord objectForKey: @"c_name"]
|
||||
forKey: @"id"];
|
||||
|
||||
// displayname || c_cn => fn
|
||||
data = [oldRecord objectForKey: @"displayname"];
|
||||
if (!data)
|
||||
data = [oldRecord objectForKey: @"c_cn"];
|
||||
if (data)
|
||||
[newRecord setObject: data forKey: @"fn"];
|
||||
else
|
||||
if (!data)
|
||||
data = @"";
|
||||
[newRecord setObject: data forKey: @"c_cn"];
|
||||
|
||||
// mail => emails[]
|
||||
data = [oldRecord objectForKey: @"c_emails"];
|
||||
if (data)
|
||||
data = [oldRecord objectForKey: @"mail"];
|
||||
if (!data)
|
||||
data = @"";
|
||||
else if ([data isKindOfClass: [NSArray class]])
|
||||
{
|
||||
if ([data isKindOfClass: [NSArray class]])
|
||||
{
|
||||
if ([data count] > 0)
|
||||
{
|
||||
NSEnumerator *emails;
|
||||
NSMutableArray *recordEmails;
|
||||
NSString *email;
|
||||
emails = [(NSArray *)data objectEnumerator];
|
||||
recordEmails = [NSMutableArray arrayWithCapacity: [data count]];
|
||||
while ((email = [emails nextObject]))
|
||||
{
|
||||
[recordEmails addObject: [NSDictionary dictionaryWithObject: email forKey: @"value"]];
|
||||
}
|
||||
[newRecord setObject: recordEmails forKey: @"emails"];
|
||||
}
|
||||
}
|
||||
else if (data)
|
||||
{
|
||||
NSDictionary *email;
|
||||
email = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil];
|
||||
[newRecord setObject: [NSArray arrayWithObject: email] forKey: @"emails"];
|
||||
}
|
||||
if ([data count] > 0)
|
||||
data = [data objectAtIndex: 0];
|
||||
else
|
||||
data = @"";
|
||||
}
|
||||
else
|
||||
data = @"";
|
||||
[newRecord setObject: data forKey: @"c_mail"];
|
||||
|
||||
data = [oldRecord objectForKey: @"nsaimid"];
|
||||
@@ -288,28 +255,25 @@
|
||||
data = @"";
|
||||
[newRecord setObject: data forKey: @"c_screenname"];
|
||||
|
||||
// o => org
|
||||
data = [oldRecord objectForKey: @"o"];
|
||||
if (data)
|
||||
[newRecord setObject: data forKey: @"org"];
|
||||
else
|
||||
if (!data)
|
||||
data = @"";
|
||||
[newRecord setObject: data forKey: @"c_o"];
|
||||
|
||||
// telephonenumber || cellphone || homephone => phones[]
|
||||
data = [oldRecord objectForKey: @"telephonenumber"];
|
||||
if (![data length])
|
||||
data = [oldRecord objectForKey: @"cellphone"];
|
||||
if (![data length])
|
||||
data = [oldRecord objectForKey: @"homephone"];
|
||||
if (data)
|
||||
{
|
||||
NSDictionary *phonenumber;
|
||||
phonenumber = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil];
|
||||
[newRecord setObject: [NSArray arrayWithObject: phonenumber] forKey: @"phones"];
|
||||
}
|
||||
else
|
||||
if (![data length])
|
||||
data = @"";
|
||||
else if ([data isKindOfClass: [NSArray class]])
|
||||
{
|
||||
if ([data count] > 0)
|
||||
data = [data objectAtIndex: 0];
|
||||
else
|
||||
data = @"";
|
||||
}
|
||||
[newRecord setObject: data forKey: @"c_telephonenumber"];
|
||||
|
||||
// Custom attribute for group-lookups. See LDAPSource.m where
|
||||
@@ -326,13 +290,9 @@
|
||||
[newRecord setObject: @"vcard" forKey: @"c_component"];
|
||||
}
|
||||
|
||||
// c_info => note
|
||||
data = [oldRecord objectForKey: @"c_info"];
|
||||
if ([data length] > 0)
|
||||
{
|
||||
[newRecord setObject: data forKey: @"note"];
|
||||
[newRecord setObject: data forKey: @"contactInfo"];
|
||||
}
|
||||
[newRecord setObject: data forKey: @"contactInfo"];
|
||||
|
||||
recordSource = [oldRecord objectForKey: @"source"];
|
||||
if ([recordSource conformsToProtocol: @protocol (SOGoDNSource)] &&
|
||||
|
||||
@@ -63,12 +63,10 @@
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoMailer.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserFolder.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
|
||||
#import <NGCards/NGVCard.h>
|
||||
|
||||
#import <Contacts/SOGoContactFolder.h>
|
||||
#import <Contacts/SOGoContactFolders.h>
|
||||
#import <Contacts/SOGoContactGCSEntry.h>
|
||||
|
||||
@@ -315,19 +313,19 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
// newHeaders come from Web form; convert priority to MIME header representation
|
||||
priority = [newHeaders objectForKey: @"priority"];
|
||||
if (!priority || [priority intValue] == 3)
|
||||
if (!priority || [priority isEqualToString: @"NORMAL"])
|
||||
{
|
||||
[headers removeObjectForKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority intValue] == 1)
|
||||
else if ([priority isEqualToString: @"HIGHEST"])
|
||||
{
|
||||
[headers setObject: @"1 (Highest)" forKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority intValue] == 2)
|
||||
else if ([priority isEqualToString: @"HIGH"])
|
||||
{
|
||||
[headers setObject: @"2 (High)" forKey: @"X-Priority"];
|
||||
}
|
||||
else if ([priority intValue] == 4)
|
||||
else if ([priority isEqualToString: @"LOW"])
|
||||
{
|
||||
[headers setObject: @"4 (Low)" forKey: @"X-Priority"];
|
||||
}
|
||||
@@ -357,7 +355,7 @@ static NSString *userAgent = nil;
|
||||
else
|
||||
{
|
||||
receipt = [newHeaders objectForKey: @"receipt"];
|
||||
if ([receipt boolValue])
|
||||
if ([receipt isEqualToString: @"true"])
|
||||
{
|
||||
[headers setObject: receipt forKey: @"receipt"];
|
||||
pureSender = [[newHeaders objectForKey: @"from"] pureEMailAddress];
|
||||
@@ -643,6 +641,7 @@ static NSString *userAgent = nil;
|
||||
[imap4URL release];
|
||||
imap4URL = nil;
|
||||
}
|
||||
[self storeInfo];
|
||||
}
|
||||
else
|
||||
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
@@ -928,6 +927,8 @@ static NSString *userAgent = nil;
|
||||
|
||||
[self setText: [sourceMail contentForEditing]];
|
||||
[self setIMAP4ID: [[sourceMail nameInContainer] intValue]];
|
||||
|
||||
[self storeInfo];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1006,11 +1007,11 @@ static NSString *userAgent = nil;
|
||||
withMetadata: attachment];
|
||||
}
|
||||
|
||||
[self storeInfo];
|
||||
|
||||
// Save the message to the IMAP store so the user can eventually view the attached file(s)
|
||||
// from the Web interface
|
||||
[self save];
|
||||
|
||||
[self storeInfo];
|
||||
}
|
||||
|
||||
/* accessors */
|
||||
@@ -1596,16 +1597,17 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
NSString *s, *dateString;
|
||||
NGMutableHashMap *map;
|
||||
id emails, from, replyTo;
|
||||
NSArray *emails;
|
||||
id from, replyTo;
|
||||
|
||||
map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
|
||||
|
||||
/* add recipients */
|
||||
if ((emails = [headers objectForKey: @"to"]) != nil && [emails isKindOfClass: [NSArray class]])
|
||||
if ((emails = [headers objectForKey: @"to"]) != nil)
|
||||
[map setObjects: [self _quoteSpecialsInArray: emails] forKey: @"to"];
|
||||
if ((emails = [headers objectForKey: @"cc"]) != nil && [emails isKindOfClass: [NSArray class]])
|
||||
if ((emails = [headers objectForKey: @"cc"]) != nil)
|
||||
[map setObjects: [self _quoteSpecialsInArray: emails] forKey: @"cc"];
|
||||
if ((emails = [headers objectForKey: @"bcc"]) != nil && [emails isKindOfClass: [NSArray class]])
|
||||
if ((emails = [headers objectForKey: @"bcc"]) != nil)
|
||||
[map setObjects: [self _quoteSpecialsInArray: emails] forKey: @"bcc"];
|
||||
|
||||
/* add senders */
|
||||
|
||||
@@ -300,7 +300,7 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
quota = quota * [(NSNumber*)[inboxQuota objectForKey: @"maxQuota"] intValue];
|
||||
inboxQuota = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithLong: (long)(quota+0.5)], @"maxQuota",
|
||||
[NSNumber numberWithLong: [[inboxQuota objectForKey: @"usedSpace"] longLongValue]], @"usedSpace",
|
||||
[inboxQuota objectForKey: @"usedSpace"], @"usedSpace",
|
||||
nil];
|
||||
}
|
||||
}
|
||||
@@ -400,147 +400,56 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
{
|
||||
NSString *folderType;
|
||||
|
||||
if ([folderName isEqualToString: inboxFolderName])
|
||||
if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", inboxFolderName]])
|
||||
folderType = @"inbox";
|
||||
else if ([folderName isEqualToString: [self draftsFolderNameInContext: context]])
|
||||
else if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", [self draftsFolderNameInContext: context]]])
|
||||
folderType = @"draft";
|
||||
else if ([folderName isEqualToString: [self sentFolderNameInContext: context]])
|
||||
else if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", [self sentFolderNameInContext: context]]])
|
||||
folderType = @"sent";
|
||||
else if ([folderName isEqualToString: [self trashFolderNameInContext: context]])
|
||||
else if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", [self trashFolderNameInContext: context]]])
|
||||
folderType = @"trash";
|
||||
else if ([folderName isEqualToString: otherUsersFolderName])
|
||||
folderType = @"otherUsers";
|
||||
else if ([folderName isEqualToString: sharedFoldersName])
|
||||
folderType = @"shared";
|
||||
else
|
||||
folderType = @"folder";
|
||||
|
||||
return folderType;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) _insertFolder: (NSString *) folderPath
|
||||
foldersList: (NSMutableArray *) theFolders
|
||||
- (NSString *) _parentForFolder: (NSString *) folderName
|
||||
foldersList: (NSArray *) theFolders
|
||||
{
|
||||
NSArray *pathComponents;
|
||||
NSMutableArray *folders;
|
||||
NSMutableDictionary *currentFolder, *parentFolder, *folder;
|
||||
NSString *currentFolderName, *currentPath, *fullName, *folderType;
|
||||
SOGoUserManager *userManager;
|
||||
int i, j, count;
|
||||
BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder;
|
||||
NSString *s;
|
||||
int i;
|
||||
|
||||
parentFolder = nil;
|
||||
parentIsOtherUsersFolder = NO;
|
||||
pathComponents = [folderPath pathComponents];
|
||||
count = [pathComponents count];
|
||||
pathComponents = [folderName pathComponents];
|
||||
s = [[[pathComponents subarrayWithRange: NSMakeRange(0,[pathComponents count]-1)] componentsJoinedByString: @"/"] substringFromIndex: 1];
|
||||
|
||||
// Make sure all ancestors exist.
|
||||
// The variable folderPath is something like '/INBOX/Junk' so pathComponents becomes ('/', 'INBOX', 'Junk').
|
||||
// That's why we always ignore the first element
|
||||
for (i = 1; i < count; i++)
|
||||
for (i = 0; i < [theFolders count]; i++)
|
||||
{
|
||||
last = ((count - i) == 1);
|
||||
folder = nil;
|
||||
currentPath = [[[pathComponents subarrayWithRange: NSMakeRange(0,i+1)] componentsJoinedByString: @"/"] substringFromIndex: 2];
|
||||
|
||||
// Search for the current path in the children of the parent folder.
|
||||
// For the first iteration, take the parent folder passed as argument.
|
||||
if (parentFolder)
|
||||
{
|
||||
folders = [parentFolder objectForKey: @"children"];
|
||||
}
|
||||
else
|
||||
{
|
||||
folders = theFolders;
|
||||
}
|
||||
|
||||
for (j = 0; j < [folders count]; j++)
|
||||
{
|
||||
currentFolder = [folders objectAtIndex: j];
|
||||
if ([currentPath isEqualToString: [currentFolder objectForKey: @"path"]])
|
||||
{
|
||||
folder = currentFolder;
|
||||
// Make sure all branches are ready to receive children
|
||||
if (!last && ![folder objectForKey: @"children"])
|
||||
{
|
||||
[folder setObject: [NSMutableArray array] forKey: @"children"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current folder is the "Other users" folder (shared mailboxes)
|
||||
currentFolderName = [[pathComponents objectAtIndex: i] stringByDecodingImap4FolderName];
|
||||
if (otherUsersFolderName
|
||||
&& [currentFolderName caseInsensitiveCompare: otherUsersFolderName] == NSOrderedSame)
|
||||
{
|
||||
isOtherUsersFolder = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
isOtherUsersFolder = NO;
|
||||
}
|
||||
|
||||
if (folder == nil)
|
||||
{
|
||||
// Folder was not found; create it and add it to the folders list
|
||||
|
||||
if (parentIsOtherUsersFolder)
|
||||
{
|
||||
// Parent folder is the "Other users" folder; translate the user's mailbox name
|
||||
// to the full name of the person
|
||||
userManager = [SOGoUserManager sharedUserManager];
|
||||
fullName = [userManager getCNForUID: currentFolderName];
|
||||
if (fullName)
|
||||
currentFolderName = fullName;
|
||||
}
|
||||
else if (isOtherUsersFolder)
|
||||
{
|
||||
currentFolderName = [self labelForKey: @"OtherUsersFolderName"];
|
||||
}
|
||||
else if (sharedFoldersName
|
||||
&& [currentFolderName caseInsensitiveCompare: sharedFoldersName] == NSOrderedSame)
|
||||
{
|
||||
currentFolderName = [self labelForKey: @"SharedFoldersName"];
|
||||
}
|
||||
|
||||
if (last)
|
||||
folderType = [self _folderType: currentPath];
|
||||
else
|
||||
folderType = @"additional";
|
||||
|
||||
folder = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
currentPath, @"path",
|
||||
folderType, @"type",
|
||||
currentFolderName, @"name",
|
||||
[NSMutableArray array], @"children",
|
||||
nil];
|
||||
// Either add this new folder to its parent or the list of root folders
|
||||
[folders addObject: folder];
|
||||
}
|
||||
|
||||
parentFolder = folder;
|
||||
parentIsOtherUsersFolder = isOtherUsersFolder;
|
||||
if ([s isEqualToString: [theFolders objectAtIndex: i]])
|
||||
return s;
|
||||
}
|
||||
|
||||
return parentFolder;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//
|
||||
// Return a tree representation of the mailboxes
|
||||
//
|
||||
//
|
||||
- (NSArray *) allFoldersMetadata
|
||||
{
|
||||
NSString *currentFolder;
|
||||
NSMutableArray *folders;
|
||||
NSString *currentFolder, *currentDecodedFolder, *currentDisplayName, *currentFolderType, *login, *fullName, *parent;
|
||||
NSMutableArray *pathComponents, *folders;
|
||||
SOGoUserManager *userManager;
|
||||
NSEnumerator *rawFolders;
|
||||
NSDictionary *folderData;
|
||||
NSAutoreleasePool *pool;
|
||||
NSArray *allFolderPaths;
|
||||
|
||||
allFolderPaths = [self allFolderPaths];
|
||||
rawFolders = [allFolderPaths objectEnumerator];
|
||||
folders = [NSMutableArray array];
|
||||
|
||||
folders = [NSMutableArray array];
|
||||
while ((currentFolder = [rawFolders nextObject]))
|
||||
{
|
||||
// Using a local pool to avoid using too many file descriptors. This could
|
||||
@@ -549,10 +458,55 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
// lots of LDAP connections during its execution.
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
// Insert folder into folders tree
|
||||
[self _insertFolder: currentFolder
|
||||
foldersList: folders];
|
||||
currentDecodedFolder = [currentFolder stringByDecodingImap4FolderName];
|
||||
currentFolderType = [self _folderType: currentFolder];
|
||||
|
||||
// We translate the "Other Users" and "Shared Folders" namespaces.
|
||||
// While we're at it, we also translate the user's mailbox names
|
||||
// to the full name of the person.
|
||||
if (otherUsersFolderName && [currentDecodedFolder hasPrefix: [NSString stringWithFormat: @"/%@", otherUsersFolderName]])
|
||||
{
|
||||
// We have a string like /Other Users/lmarcotte/... under Cyrus, but we could
|
||||
// also have something like /shared under Dovecot. So we swap the username only
|
||||
// if we have one, of course.
|
||||
pathComponents = [NSMutableArray arrayWithArray: [currentDecodedFolder pathComponents]];
|
||||
|
||||
if ([pathComponents count] > 2)
|
||||
{
|
||||
login = [pathComponents objectAtIndex: 2];
|
||||
userManager = [SOGoUserManager sharedUserManager];
|
||||
fullName = [userManager getCNForUID: login];
|
||||
[pathComponents removeObjectsInRange: NSMakeRange(0,3)];
|
||||
|
||||
currentDisplayName = [NSString stringWithFormat: @"/%@/%@/%@",
|
||||
[self labelForKey: @"OtherUsersFolderName"],
|
||||
(fullName != nil ? fullName : login),
|
||||
[pathComponents componentsJoinedByString: @"/"]];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
currentDisplayName = [NSString stringWithFormat: @"/%@%@",
|
||||
[self labelForKey: @"OtherUsersFolderName"],
|
||||
[currentDecodedFolder substringFromIndex:
|
||||
[otherUsersFolderName length]+1]];
|
||||
}
|
||||
}
|
||||
else if (sharedFoldersName && [currentDecodedFolder hasPrefix: [NSString stringWithFormat: @"/%@", sharedFoldersName]])
|
||||
currentDisplayName = [NSString stringWithFormat: @"/%@%@", [self labelForKey: @"SharedFoldersName"],
|
||||
[currentDecodedFolder substringFromIndex: [sharedFoldersName length]+1]];
|
||||
else
|
||||
currentDisplayName = currentDecodedFolder;
|
||||
|
||||
parent = [self _parentForFolder: currentFolder foldersList: allFolderPaths];
|
||||
|
||||
folderData = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
currentFolder, @"path",
|
||||
currentFolderType, @"type",
|
||||
currentDisplayName, @"displayName",
|
||||
parent, @"parent",
|
||||
nil];
|
||||
[folders addObject: folderData];
|
||||
[pool release];
|
||||
}
|
||||
|
||||
@@ -735,7 +689,7 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
result = [client lstatus: @"*" flags: [NSArray arrayWithObjects: @"x-guid", nil]];
|
||||
|
||||
e = [folderList objectEnumerator];
|
||||
|
||||
|
||||
while ((object = [e nextObject]))
|
||||
{
|
||||
if (hasAnnotatemore)
|
||||
@@ -845,12 +799,12 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
accounts = [user mailAccounts];
|
||||
accountIdx = [nameInContainer intValue];
|
||||
account = [accounts objectAtIndex: accountIdx];
|
||||
folderName = [[account objectForKey: @"specialMailboxes"]
|
||||
folderName = [[account objectForKey: @"mailboxes"]
|
||||
objectForKey: purpose];
|
||||
if (!folderName && accountIdx > 0)
|
||||
{
|
||||
account = [accounts objectAtIndex: 0];
|
||||
folderName = [[account objectForKey: @"specialMailboxes"]
|
||||
folderName = [[account objectForKey: @"mailboxes"]
|
||||
objectForKey: purpose];
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
- (NSArray *) toManyRelationshipKeys;
|
||||
|
||||
- (NSDictionary *) statusForFlags: (NSArray *) flags;
|
||||
- (unsigned int) unseenCount;
|
||||
|
||||
- (NSException *) deleteUIDs: (NSArray *) uids
|
||||
useTrashFolder: (BOOL *) withTrash
|
||||
@@ -97,9 +96,10 @@
|
||||
|
||||
- (NSString *) davCollectionTag;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate;
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress;
|
||||
|
||||
/* flags */
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserFolder.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
#import <SOGo/WOResponse+SOGo.h>
|
||||
@@ -65,7 +64,6 @@
|
||||
#import "EOQualifier+MailDAV.h"
|
||||
#import "SOGoMailObject.h"
|
||||
#import "SOGoMailAccount.h"
|
||||
#import "SOGoMailAccounts.h"
|
||||
#import "SOGoMailManager.h"
|
||||
#import "SOGoMailFolder.h"
|
||||
#import "SOGoTrashFolder.h"
|
||||
@@ -803,33 +801,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return status;
|
||||
}
|
||||
|
||||
- (unsigned int) unseenCount
|
||||
{
|
||||
NSDictionary *imapResult;
|
||||
NGImap4Connection *connection;
|
||||
NGImap4Client *client;
|
||||
EOQualifier *searchQualifier;
|
||||
NSArray *searchResult;
|
||||
unsigned int unseen;
|
||||
|
||||
connection = [self imap4Connection];
|
||||
client = [connection client];
|
||||
|
||||
if ([connection selectFolder: [self imap4URL]])
|
||||
{
|
||||
searchQualifier
|
||||
= [EOQualifier qualifierWithQualifierFormat: @"flags = %@ AND not flags = %@",
|
||||
@"unseen", @"deleted"];
|
||||
imapResult = [client searchWithQualifier: searchQualifier];
|
||||
searchResult = [[imapResult objectForKey: @"RawResponse"] objectForKey: @"search"];
|
||||
unseen = [searchResult count];
|
||||
}
|
||||
else
|
||||
unseen = 0;
|
||||
|
||||
return unseen;
|
||||
}
|
||||
|
||||
- (NSArray *) fetchUIDsMatchingQualifier: (id) _q
|
||||
sortOrdering: (id) _so
|
||||
{
|
||||
@@ -2119,6 +2090,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress
|
||||
{
|
||||
EOQualifier *searchQualifier;
|
||||
NSMutableArray *allTokens;
|
||||
@@ -2195,6 +2167,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
|
||||
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"]
|
||||
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
|
||||
[allTokens addObject: d];
|
||||
@@ -2205,7 +2180,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
if (highestmodseq == 0)
|
||||
highestmodseq = 1;
|
||||
|
||||
if (highestmodseq > 0)
|
||||
if (highestmodseq > 0 && !initialLoadInProgress)
|
||||
{
|
||||
id uid;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2013-2015 Inverse inc.
|
||||
Copyright (C) 2013 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
- (NSString *) color;
|
||||
|
||||
+ (NSArray *) labelsFromDefaults: (NSDictionary *) theDefaults
|
||||
component: (id) theComponent;
|
||||
component: (UIxComponent *) theComponent;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2015 Inverse inc.
|
||||
Copyright (C) 2007-2013 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
|
||||
+ (NSArray *) labelsFromDefaults: (NSDictionary *) theDefaults
|
||||
component: (id) theComponent
|
||||
component: (UIxComponent *) theComponent
|
||||
{
|
||||
NSMutableArray *allLabels, *allKeys;
|
||||
NSString *key, *name;
|
||||
@@ -81,6 +81,7 @@
|
||||
key = [allKeys objectAtIndex: i];
|
||||
values = [theDefaults objectForKey: key];
|
||||
name = [theComponent commonLabelForKey: [values objectAtIndex: 0]];
|
||||
|
||||
label = [[self alloc] initWithName: key
|
||||
label: name
|
||||
color: [values objectAtIndex: 1]];
|
||||
|
||||
@@ -766,7 +766,6 @@ static BOOL debugSoParts = NO;
|
||||
[part objectForKey: @"subtype"]];
|
||||
|
||||
if (!filename)
|
||||
{
|
||||
// We might end up here because of MUA that actually strips the
|
||||
// Content-Disposition (and thus, the filename) when mails containing
|
||||
// attachments have been forwarded. Thunderbird (2.x) does just that
|
||||
@@ -778,7 +777,7 @@ static BOOL debugSoParts = NO;
|
||||
filename = [NSString stringWithFormat: @"unknown_%@", path];
|
||||
else if ([mimeType isEqualToString: @"message/rfc822"])
|
||||
filename = [NSString stringWithFormat: @"email_%@.eml", path];
|
||||
}
|
||||
|
||||
|
||||
if (filename)
|
||||
{
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/* CardElement+SOGo.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef CARDELEMENT_SOGO_H
|
||||
#define CARDELEMENT_SOGO_H
|
||||
|
||||
#import <NGCards/CardElement.h>
|
||||
|
||||
@interface CardElement (SOGoExtensions)
|
||||
|
||||
- (NSString *) jsonRepresentation;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* CARDELEMENT_SOGO_H */
|
||||
@@ -1,54 +0,0 @@
|
||||
/* CardElement+SOGo.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#import "NSDictionary+Utilities.h"
|
||||
|
||||
#import "CardElement+SOGo.h"
|
||||
|
||||
|
||||
@implementation CardElement (SOGoExtensions)
|
||||
|
||||
/**
|
||||
* Return the JSON representation of the element as a dictionary with two keys:
|
||||
* - type: the type of the card element, if it exists;
|
||||
* - value: the value of the card element.
|
||||
* @return a JSON string representation of the element.
|
||||
*/
|
||||
- (NSString *) jsonRepresentation
|
||||
{
|
||||
NSString *v;
|
||||
NSMutableDictionary *attrs;
|
||||
|
||||
attrs = [NSMutableDictionary dictionary];
|
||||
|
||||
v = [self value: 0 ofAttribute: @"type"];
|
||||
if ([v length])
|
||||
[attrs setObject: v
|
||||
forKey: @"type"];
|
||||
[attrs setObject: [self flattenedValuesForKey: @""]
|
||||
forKey: @"value"];
|
||||
|
||||
return [attrs jsonRepresentation];
|
||||
}
|
||||
|
||||
@end /* NGVCard */
|
||||
@@ -72,7 +72,6 @@ SOGo_HEADER_FILES = \
|
||||
SOGoGroup.h \
|
||||
SOGoUser.h \
|
||||
\
|
||||
CardElement+SOGo.h \
|
||||
DOMNode+SOGo.h \
|
||||
\
|
||||
WORequest+SOGo.h \
|
||||
@@ -86,7 +85,7 @@ all::
|
||||
@touch SOGoBuild.m
|
||||
|
||||
SOGo_OBJC_FILES = \
|
||||
SOGoBuild.m \
|
||||
SOGoBuild.m \
|
||||
SOGoProductLoader.m \
|
||||
\
|
||||
BSONCodec.m \
|
||||
@@ -151,7 +150,6 @@ SOGo_OBJC_FILES = \
|
||||
SOGoGroup.m \
|
||||
SOGoUser.m \
|
||||
\
|
||||
CardElement+SOGo.m \
|
||||
DOMNode+SOGo.m \
|
||||
\
|
||||
WORequest+SOGo.m \
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
|
||||
- (NSString *) shortDateString;
|
||||
- (NSString *) rfc822DateString;
|
||||
- (NSString *) iso8601DateString;
|
||||
|
||||
+ (id) distantFuture;
|
||||
+ (id) distantPast;
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
|
||||
#import "NSCalendarDate+SOGo.h"
|
||||
@@ -115,34 +113,6 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
|
||||
timeZoneShift];
|
||||
}
|
||||
|
||||
- (NSString *) iso8601DateString
|
||||
{
|
||||
char buf[22];
|
||||
int timeZoneHourShift, timeZoneMinuteShift, tzSeconds;
|
||||
NSNumber *day, *month, *year, *hour, *minute;
|
||||
|
||||
day = [NSNumber numberWithInt: [self dayOfMonth]];
|
||||
month = [NSNumber numberWithInt: [self monthOfYear]];
|
||||
year = [NSNumber numberWithInt: [self yearOfCommonEra]];
|
||||
hour = [NSNumber numberWithInt: [self hourOfDay]];
|
||||
minute = [NSNumber numberWithInt: [self minuteOfHour]];
|
||||
|
||||
tzSeconds = [[self timeZone] secondsFromGMT];
|
||||
timeZoneHourShift = (tzSeconds / 3600);
|
||||
tzSeconds -= timeZoneHourShift * 3600;
|
||||
timeZoneMinuteShift = tzSeconds / 60;
|
||||
|
||||
sprintf(buf, "%04d-%02d-%02dT%02d:%02d%+.2d:%02d",
|
||||
[year intValue],
|
||||
[month intValue],
|
||||
[day intValue],
|
||||
[hour intValue],
|
||||
[minute intValue],
|
||||
timeZoneHourShift,
|
||||
timeZoneMinuteShift);
|
||||
|
||||
return [NSString stringWithCString:buf];
|
||||
}
|
||||
|
||||
#define secondsOfDistantFuture 1073741823.0
|
||||
#define secondsOfDistantPast -1073741823.0
|
||||
|
||||
@@ -373,6 +373,7 @@ static int cssEscapingCount;
|
||||
|
||||
- (NSString *) asCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *cssIdentifier;
|
||||
unichar currentChar;
|
||||
int count, max, idx;
|
||||
@@ -381,10 +382,12 @@ static int cssEscapingCount;
|
||||
[self _setupCSSEscaping];
|
||||
|
||||
cssIdentifier = [NSMutableString string];
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
max = [self length];
|
||||
|
||||
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
|
||||
[cssIdentifier appendString: @"_"];
|
||||
for (count = 0; count < max; count++)
|
||||
@@ -415,6 +418,7 @@ static int cssEscapingCount;
|
||||
|
||||
- (NSString *) fromCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *newString;
|
||||
NSString *currentString;
|
||||
int count, length, max, idx;
|
||||
@@ -423,12 +427,14 @@ static int cssEscapingCount;
|
||||
if (!cssEscapingStrings)
|
||||
[self _setupCSSEscaping];
|
||||
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
newString = [NSMutableString string];
|
||||
max = [self length];
|
||||
count = 0;
|
||||
|
||||
if (max > 0
|
||||
&& [self characterAtIndex: 0] == '_'
|
||||
&& isdigit([self characterAtIndex: 1]))
|
||||
&& [numericSet characterIsMember: [self characterAtIndex: 1]])
|
||||
{
|
||||
/* If the identifier starts with an underscore followed by a digit,
|
||||
we remove the underscore */
|
||||
|
||||
@@ -51,8 +51,6 @@
|
||||
SOGoTimeFormat = "%H:%M";
|
||||
SOGoFirstDayOfWeek = 0;
|
||||
SOGoFirstWeekOfYear = "January1";
|
||||
SOGoShortDateFormat = "%d-%b-%y";
|
||||
SOGoLongDateFormat = "%A, %B %d, %Y";
|
||||
|
||||
SOGoIMAPServer = "localhost";
|
||||
SOGoMailDomain = "localhost";
|
||||
@@ -69,8 +67,6 @@
|
||||
SOGoSentFolderName = "Sent";
|
||||
SOGoDraftsFolderName = "Drafts";
|
||||
SOGoTrashFolderName = "Trash";
|
||||
SOGoMailComposeMessageType = "html";
|
||||
SOGoMailDisplayRemoteInlineImages = "never";
|
||||
|
||||
SOGoMailAutoSave = "5";
|
||||
|
||||
@@ -78,10 +74,8 @@
|
||||
SOGoCalendarEventsDefaultClassification = "PUBLIC";
|
||||
SOGoCalendarTasksDefaultClassification = "PUBLIC";
|
||||
SOGoCalendarDefaultReminder = "NONE";
|
||||
SOGoDefaultCalendar = "selected";
|
||||
SOGoFreeBusyDefaultInterval = ( 7, 7 );
|
||||
|
||||
SOGoSelectedAddressBook = "personal";
|
||||
SOGoFreeBusyDefaultInterval = ( 7, 7 );
|
||||
|
||||
SOGoSearchMinimumWordLength = 2;
|
||||
|
||||
@@ -93,8 +87,6 @@
|
||||
$label5 = ("Later", "#993399");
|
||||
};
|
||||
|
||||
SOGoCalendarCategories = ("Customer", "Calls", "Favorites", "Meeting", "Ideas", "Miscellaneous", "Birthday", "Anniversary", "Vacation", "Travel", "Projects", "Suppliers", "Gifts", "Clients", "Issues", "Business", "Holidays", "Personal", "Status", "Competition", "Follow up", "Public Holiday");
|
||||
|
||||
SOGoCalendarCategoriesColors = { "Customer" = "#F0F0F0"; "Calls" = "#F0F0F0"; "Favorites" = "#F0F0F0"; "Meeting" = "#F0F0F0"; "Ideas" = "#F0F0F0"; "Miscellaneous" = "#F0F0F0"; "Birthday" = "#F0F0F0"; "Anniversary" = "#F0F0F0"; "Vacation" = "#F0F0F0"; "Travel" = "#F0F0F0"; "Projects" = "#F0F0F0"; "Suppliers" = "#F0F0F0"; "Gifts" = "#F0F0F0"; "Clients" = "#F0F0F0"; "Issues" = "#F0F0F0"; "Business" = "#F0F0F0"; "Holidays" = "#F0F0F0"; "Personal" = "#F0F0F0"; "Status" = "#F0F0F0"; "Competition" = "#F0F0F0"; "Follow up" = "#F0F0F0"; "Public Holiday" = "#F0F0F0"; };
|
||||
|
||||
SOGoSubscriptionFolderFormat = "%{FolderName} (%{UserName} <%{Email}>)";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* SOGoDefaultsSource.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* SOGoDefaultsSource.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -128,8 +128,8 @@
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate;
|
||||
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress;
|
||||
|
||||
/* multiget helper */
|
||||
- (WOResponse *) performMultigetInContext: (WOContext *) queryContext
|
||||
|
||||
@@ -1151,6 +1151,7 @@ static NSArray *childRecordFields = nil;
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress
|
||||
{
|
||||
/* TODO:
|
||||
- validation:
|
||||
@@ -1200,13 +1201,17 @@ static NSArray *childRecordFields = nil;
|
||||
mRecords = [NSMutableArray arrayWithArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: YES]];
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"c_lastmodified > %d and c_deleted == 1",
|
||||
syncTokenInt];
|
||||
fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_deleted", nil];
|
||||
[mRecords addObjectsFromArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: NO]];
|
||||
if (!initialLoadInProgress)
|
||||
{
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"c_lastmodified > %d and c_deleted == 1",
|
||||
syncTokenInt];
|
||||
fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_lastmodified", @"c_deleted", nil];
|
||||
[mRecords addObjectsFromArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: NO]];
|
||||
}
|
||||
|
||||
records = mRecords;
|
||||
}
|
||||
else
|
||||
@@ -1499,7 +1504,9 @@ static NSArray *childRecordFields = nil;
|
||||
properties = [self parseDAVRequestedProperties: propElement];
|
||||
records = [self syncTokenFieldsWithProperties: properties
|
||||
matchingSyncToken: syncToken
|
||||
fromDate: nil];
|
||||
fromDate: nil
|
||||
initialLoad: NO];
|
||||
|
||||
[self _appendComponentProperties: [properties allKeys]
|
||||
fromRecords: records
|
||||
matchingSyncToken: [syncToken intValue]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* SOGoSQLUserProfile.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* SOGoSQLUserProfile.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
- (NSString *) cn;
|
||||
|
||||
- (SOGoDateFormatter *) dateFormatterInContext: (WOContext *) context;
|
||||
- (NSDictionary *) currentDay;
|
||||
|
||||
/* defaults */
|
||||
- (SOGoUserDefaults *) userDefaults;
|
||||
|
||||
@@ -394,39 +394,6 @@
|
||||
return dateFormatter;
|
||||
}
|
||||
|
||||
- (NSDictionary *) currentDay
|
||||
{
|
||||
NSCalendarDate *now;
|
||||
NSDictionary *description, *abbr;
|
||||
NSDictionary *locale;
|
||||
SOGoUserDefaults *ud;
|
||||
WOResourceManager *resMgr;
|
||||
NSUInteger seconds;
|
||||
|
||||
now = [NSCalendarDate calendarDate];
|
||||
ud = [self userDefaults];
|
||||
resMgr = [[WOApplication application] resourceManager];
|
||||
locale = [resMgr localeForLanguageNamed: [ud language]];
|
||||
|
||||
[now setTimeZone: [ud timeZone]];
|
||||
seconds = [now hourOfDay]*3600 + [now minuteOfHour]*60 + [now secondOfMinute];
|
||||
|
||||
abbr = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[now descriptionWithCalendarFormat: @"%a" locale: locale], @"weekday",
|
||||
[now descriptionWithCalendarFormat: @"%b" locale: locale], @"month",
|
||||
nil];
|
||||
description = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[now descriptionWithCalendarFormat: @"%A" locale: locale], @"weekday",
|
||||
[now descriptionWithCalendarFormat: @"%B" locale: locale], @"month",
|
||||
[now descriptionWithCalendarFormat: @"%d" locale: locale], @"day",
|
||||
[now descriptionWithCalendarFormat: @"%Y" locale: locale], @"year",
|
||||
abbr, @"abbr",
|
||||
[NSNumber numberWithInt: (24*3600 - seconds)], @"secondsBeforeTomorrow",
|
||||
nil];
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
- (SOGoUserDefaults *) userDefaults
|
||||
{
|
||||
if (!_defaults)
|
||||
@@ -627,10 +594,10 @@
|
||||
{
|
||||
NSString *fullName, *replyTo, *imapLogin, *imapServer, *cImapServer, *signature,
|
||||
*encryption, *scheme, *action, *query, *customEmail, *defaultEmail, *sieveServer;
|
||||
NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts, *mailSettings;
|
||||
NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts;
|
||||
NSNumber *port;
|
||||
NSMutableArray *identities;
|
||||
NSArray *mails, *delegates;
|
||||
NSArray *mails;
|
||||
NSURL *url, *cUrl;
|
||||
unsigned int count, max, default_identity;
|
||||
NSInteger defaultPort;
|
||||
@@ -819,34 +786,11 @@
|
||||
forKey: @"Sent"];
|
||||
[mailboxes setObject: [_defaults trashFolderName]
|
||||
forKey: @"Trash"];
|
||||
[mailAccount setObject: mailboxes forKey: @"specialMailboxes"];
|
||||
[mailAccount setObject: mailboxes forKey: @"mailboxes"];
|
||||
[mailboxes release];
|
||||
|
||||
[mailAccounts addObject: mailAccount];
|
||||
[mailAccount release];
|
||||
|
||||
/* delegates */
|
||||
mailSettings = [[self userSettings] objectForKey: @"Mail"];
|
||||
delegates = [mailSettings objectForKey: @"DelegateTo"];
|
||||
if (!delegates)
|
||||
delegates = [NSArray array];
|
||||
else
|
||||
{
|
||||
NSMutableArray *allDelegates;
|
||||
SOGoUser *delegate;
|
||||
|
||||
allDelegates = [NSMutableArray array];
|
||||
for (count = 0; count < [delegates count]; count++)
|
||||
{
|
||||
delegate = [SOGoUser userWithLogin: [delegates objectAtIndex: count]];
|
||||
[allDelegates addObject: [NSDictionary dictionaryWithObjectsAndKeys: [delegates objectAtIndex: count], @"uid",
|
||||
[delegate cn], @"cn",
|
||||
[delegate systemEmail], @"c_email", nil]];
|
||||
}
|
||||
|
||||
delegates = allDelegates;
|
||||
}
|
||||
[mailAccount setObject: delegates forKey: @"delegates"];
|
||||
}
|
||||
|
||||
- (NSArray *) mailAccounts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoUserDefaults.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2015 Inverse inc.
|
||||
* Copyright (C) 2011-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoUserDefaults.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
* Copyright (C) 2009-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -449,6 +449,8 @@ static Class NSNullK;
|
||||
NSEnumerator *authIDs;
|
||||
NSString *currentID;
|
||||
BOOL checkOK;
|
||||
SOGoSystemDefaults *sd;
|
||||
NSRange r;
|
||||
|
||||
checkOK = NO;
|
||||
|
||||
@@ -778,7 +780,10 @@ static Class NSNullK;
|
||||
NSRange r;
|
||||
|
||||
r = [theUID rangeOfString: [NSString stringWithFormat: @"@%@", theDomain]];
|
||||
theUID = [theUID substringToIndex: r.location];
|
||||
|
||||
// We check if the range is ok here since we could be using DomainLessLogin
|
||||
if (r.location != NSNotFound)
|
||||
theUID = [theUID substringToIndex: r.location];
|
||||
}
|
||||
|
||||
sogoSources = [[self authenticationSourceIDsInDomain: theDomain] objectEnumerator];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright (C) 2008-2015 Inverse inc.
|
||||
Copyright (C) 2008-2009 Inverse inc.
|
||||
Copyright (C) 2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -80,7 +81,6 @@ typedef enum _SOGoUserProfileType {
|
||||
|
||||
/* saving changes */
|
||||
|
||||
- (BOOL) dirty;
|
||||
- (BOOL) synchronize;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright (C) 2008-2015 Inverse inc.
|
||||
Copyright (C) 2008-2009 Inverse inc.
|
||||
Copyright (C) 2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -322,10 +323,6 @@
|
||||
}
|
||||
|
||||
/* saving changes */
|
||||
- (BOOL) dirty
|
||||
{
|
||||
return defFlags.modified;
|
||||
}
|
||||
|
||||
- (BOOL) synchronize
|
||||
{
|
||||
|
||||
@@ -38,23 +38,10 @@
|
||||
NSMutableArray *languages;
|
||||
NSArray *browserLanguages;
|
||||
SOGoUser *user;
|
||||
NSString *language, *theme;
|
||||
NSString *language;
|
||||
|
||||
languages = [NSMutableArray array];
|
||||
user = [self activeUser];
|
||||
theme = [[self request] formValueForKey: @"theme"];
|
||||
|
||||
if ([theme length] > 0)
|
||||
{
|
||||
if ([theme hasSuffix: @"/"])
|
||||
{
|
||||
// Remove trailing slash
|
||||
theme = [theme substringToIndex: [theme length] - 1];
|
||||
}
|
||||
[languages addObject: [NSString stringWithFormat: @"theme_%@", theme]];
|
||||
[self setObject: theme forKey: @"theme"];
|
||||
}
|
||||
|
||||
if (!user || [[user login] isEqualToString: @"anonymous"])
|
||||
{
|
||||
browserLanguages = [[self request] browserLanguages];
|
||||
@@ -68,6 +55,14 @@
|
||||
[languages addObject: language];
|
||||
}
|
||||
|
||||
// if (activeUser && [activeUser language])
|
||||
// [languages addObject: [activeUser language]];
|
||||
|
||||
// if ([self hasSession])
|
||||
// [languages addObjectsFromArray: [[self session] languages]];
|
||||
// else
|
||||
// [languages addObjectsFromArray: [[self request] browserLanguages]];
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
from config import hostname, port, username, password
|
||||
import webdavlib
|
||||
import simplejson
|
||||
import sogoLogin
|
||||
|
||||
|
||||
DEBUG=True
|
||||
DEBUG=False
|
||||
|
||||
|
||||
class HTTPPreferencesPOST (webdavlib.HTTPPOST):
|
||||
cookie = None
|
||||
|
||||
def prepare_headers (self):
|
||||
headers = webdavlib.HTTPPOST.prepare_headers(self)
|
||||
if self.cookie:
|
||||
headers["Cookie"] = self.cookie
|
||||
return headers
|
||||
|
||||
class HTTPPreferencesGET (webdavlib.HTTPGET):
|
||||
cookie = None
|
||||
|
||||
def prepare_headers (self):
|
||||
headers = webdavlib.HTTPGET.prepare_headers(self)
|
||||
if self.cookie:
|
||||
headers["Cookie"] = self.cookie
|
||||
return headers
|
||||
|
||||
class Carddav:
|
||||
login = username
|
||||
passw = password
|
||||
|
||||
def __init__(self, otherLogin = None, otherPassword = None):
|
||||
if otherLogin and otherPassword:
|
||||
self.login = otherLogin
|
||||
self.passw = otherPassword
|
||||
|
||||
self.client = webdavlib.WebDAVClient(hostname, port)
|
||||
authCookie = sogoLogin.getAuthCookie(hostname, port, self.login, self.passw)
|
||||
self.cookie = authCookie
|
||||
self.cards = None
|
||||
self.calendars = None
|
||||
self.fields = None
|
||||
self.events = None
|
||||
|
||||
#- If this is not set, we CAN'T save preferences
|
||||
self.preferences = None
|
||||
|
||||
def _get(self, url):
|
||||
get = HTTPPreferencesGET(url)
|
||||
get.cookie = self.cookie
|
||||
self.client.execute(get)
|
||||
if DEBUG: print "(url):", url
|
||||
if DEBUG: print "(status):", get.response["status"]
|
||||
if DEBUG: print "(body):", get.response['body']
|
||||
content = simplejson.loads(get.response['body'])
|
||||
return content
|
||||
|
||||
def _post(self, url, data, errormsg="failure POST"):
|
||||
if DEBUG: print "URL:", url
|
||||
post = HTTPPreferencesPOST(url, simplejson.dumps(data))
|
||||
post.content_type = "application/json"
|
||||
post.cookie = self.cookie
|
||||
self.client.execute(post)
|
||||
# Raise an exception if the pref wasn't properly set
|
||||
if post.response["status"] != 200:
|
||||
raise Exception ("%s, (code = %d)" % (errormsg, post.response["status"]))
|
||||
|
||||
def load_cards(self):
|
||||
if not self.cards:
|
||||
url = "/SOGo/so/%s/Contacts/personal/view" % (self.login)
|
||||
content = self._get(url)
|
||||
self.cards = content['cards']
|
||||
return self.cards
|
||||
|
||||
def get_cards(self, pattern):
|
||||
self.load_cards()
|
||||
return [a for a in self.cards if pattern in a.values()]
|
||||
|
||||
def get_card(self, idstr):
|
||||
url = "/SOGo/so/%s/Contacts/personal/%s/view" % (self.login, idstr)
|
||||
content = self._get(url)
|
||||
return content
|
||||
|
||||
def save_card(self, card):
|
||||
url = "/SOGo/so/%s/Contacts/personal/%s/saveAsContact" % (self.login, card['id'])
|
||||
self._post(url, card, "failure saving card")
|
||||
|
||||
def load_calendars(self):
|
||||
if not self.calendars:
|
||||
url = "/SOGo/so/%s/Calendar/calendarslist" % (self.login)
|
||||
content = self._get(url)
|
||||
self.calendars = content['calendars']
|
||||
return self.calendars
|
||||
|
||||
def get_calendars(self, pattern):
|
||||
self.load_calendars()
|
||||
return [a for a in self.calendars if pattern in a.values()]
|
||||
|
||||
def get_calendar(self, idstr):
|
||||
self.load_calendars()
|
||||
callist = [a for a in self.calendars if a['id'] == idstr]
|
||||
if len(callist):
|
||||
return callist[0]
|
||||
return None
|
||||
|
||||
def save_calendar(self, calendar):
|
||||
url = "/SOGo/so/%s/Contacts/personal/%s/saveAsContact" % (self.login, calendar['id'])
|
||||
self._post(url, card, "failure saving calendar")
|
||||
|
||||
def load_events(self):
|
||||
if not self.events:
|
||||
url = "/SOGo/so/%s/Calendar/eventslist" % (self.login)
|
||||
content = self._get(url)
|
||||
tmp_events = content['events']
|
||||
self.fields = content['fields']
|
||||
self.events = [dict(zip(self.fields, event)) for event in tmp_events]
|
||||
return self.events
|
||||
|
||||
def newguid(self, folderpath):
|
||||
url = "/SOGo/so/%s/%s/newguid" % (self.login, folderpath)
|
||||
content = self._get(url)
|
||||
return content['id']
|
||||
|
||||
def save_event(self, event, folder, gid):
|
||||
#url = "/SOGo/so/%s/%s/%s/save" % (self.login, event['c_folder'], event['c_name'])
|
||||
url = "/SOGo/so/%s/%s/%s/saveAsAppointment" % (self.login, folder, gid)
|
||||
self._post(url, event, "failure saving event")
|
||||
|
||||
@@ -7,9 +7,6 @@ import simplejson
|
||||
import sogoLogin
|
||||
|
||||
|
||||
DEBUG=False
|
||||
#DEBUG=True
|
||||
|
||||
|
||||
# must be kept in sync with SoObjects/SOGo/SOGoDefaults.plist
|
||||
# this should probably be fetched magically...
|
||||
@@ -21,142 +18,122 @@ SOGoSupportedLanguages = [ "Arabic", "Basque", "Catalan", "ChineseTaiwan", "Czec
|
||||
daysBetweenResponseList=[1,2,3,5,7,14,21,30]
|
||||
|
||||
class HTTPPreferencesPOST (webdavlib.HTTPPOST):
|
||||
cookie = None
|
||||
|
||||
def prepare_headers (self):
|
||||
headers = webdavlib.HTTPPOST.prepare_headers(self)
|
||||
if self.cookie:
|
||||
headers["Cookie"] = self.cookie
|
||||
return headers
|
||||
cookie = None
|
||||
|
||||
def prepare_headers (self):
|
||||
headers = webdavlib.HTTPPOST.prepare_headers(self)
|
||||
if self.cookie:
|
||||
headers["Cookie"] = self.cookie
|
||||
return headers
|
||||
|
||||
class HTTPPreferencesGET (webdavlib.HTTPGET):
|
||||
cookie = None
|
||||
cookie = None
|
||||
|
||||
def prepare_headers (self):
|
||||
headers = webdavlib.HTTPGET.prepare_headers(self)
|
||||
if self.cookie:
|
||||
headers["Cookie"] = self.cookie
|
||||
return headers
|
||||
def prepare_headers (self):
|
||||
headers = webdavlib.HTTPGET.prepare_headers(self)
|
||||
if self.cookie:
|
||||
headers["Cookie"] = self.cookie
|
||||
return headers
|
||||
|
||||
class preferences:
|
||||
login = username
|
||||
passw = password
|
||||
login = username
|
||||
passw = password
|
||||
|
||||
def __init__(self, otherLogin = None, otherPassword = None):
|
||||
if otherLogin and otherPassword:
|
||||
self.login = otherLogin
|
||||
self.passw = otherPassword
|
||||
def __init__(self, otherLogin = None, otherPassword = None):
|
||||
if otherLogin and otherPassword:
|
||||
self.login = otherLogin
|
||||
self.passw = otherPassword
|
||||
|
||||
self.client = webdavlib.WebDAVClient(hostname, port)
|
||||
self.client = webdavlib.WebDAVClient(hostname, port)
|
||||
|
||||
authCookie = sogoLogin.getAuthCookie(hostname, port, self.login, self.passw)
|
||||
self.cookie = authCookie
|
||||
authCookie = sogoLogin.getAuthCookie(hostname, port, self.login, self.passw)
|
||||
self.cookie = authCookie
|
||||
|
||||
#- If this is not set, we CAN'T save preferences
|
||||
self.preferences = None
|
||||
# map between preferences/jsonDefaults and the webUI names
|
||||
# should probably be unified...
|
||||
self.preferencesMap = {
|
||||
"SOGoLanguage": "language",
|
||||
"SOGoTimeZone": "timezone",
|
||||
"SOGoSieveFilters": "sieveFilters",
|
||||
|
||||
def find_key(self, d, key):
|
||||
if key in d:
|
||||
return d
|
||||
subdicts = [a[1] for a in d.iteritems() if type(a[1]) == dict]
|
||||
for subd in subdicts:
|
||||
ret = self.find_key(subd, key)
|
||||
if ret:
|
||||
return ret
|
||||
return None
|
||||
# Vacation stuff
|
||||
"Vacation": "enableVacation", # to disable, don't specify it
|
||||
"autoReplyText": "autoReplyText", # string
|
||||
"autoReplyEmailAddresses": "autoReplyEmailAddresses", # LIST
|
||||
"daysBetweenResponse": "daysBetweenResponsesList",
|
||||
"ignoreLists": "ignoreLists", #bool
|
||||
|
||||
def load_preferences(self):
|
||||
defaults = self.get_defaults()
|
||||
settings = self.get_settings()
|
||||
self.preferences = {'defaults': defaults, 'settings': settings}
|
||||
#print "LOAD PREFS:", self.preferences
|
||||
# forward stuff
|
||||
"Forward": "enableForward", # to disable, don't specify it
|
||||
"forwardAddress": "forwardAddress",
|
||||
"keepCopy": "forwardKeepCopy",
|
||||
|
||||
def get(self, preference=None):
|
||||
if not self.preferences:
|
||||
self.load_preferences()
|
||||
#- Want the whole thing
|
||||
if not preference:
|
||||
return self.preferences
|
||||
else:
|
||||
tmpdict = self.find_key(self.preferences, preference)
|
||||
if tmpdict:
|
||||
return tmpdict[preference]
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get(self, subtype='jsonDefault', preference=None):
|
||||
url = "/SOGo/so/%s/%s" % (self.login, subtype)
|
||||
get = HTTPPreferencesGET(url)
|
||||
get.cookie = self.cookie
|
||||
self.client.execute(get)
|
||||
if DEBUG: print "LUC (url):", url
|
||||
if DEBUG: print "LUC (status):", get.response["status"]
|
||||
if DEBUG: print "LUC (body):", get.response['body']
|
||||
content = simplejson.loads(get.response['body'])
|
||||
result = None
|
||||
try:
|
||||
if preference:
|
||||
result = content[preference]
|
||||
else:
|
||||
result = content
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
# Calendar stuff
|
||||
"enablePreventInvitations": "preventInvitations",
|
||||
"PreventInvitations": "PreventInvitations",
|
||||
"whiteList": "whiteList",
|
||||
}
|
||||
|
||||
def get_defaults(self, preference=None):
|
||||
return self._get('jsonDefaults', preference)
|
||||
def set(self, preference, value=None):
|
||||
# if preference is a dict, set all prefs found in the dict
|
||||
content=""
|
||||
if isinstance(preference, dict):
|
||||
for k,v in preference.items():
|
||||
content+="%s=%s&" % (self.preferencesMap[k], urllib.quote(v))
|
||||
else:
|
||||
# assume it is a str
|
||||
formKey = self.preferencesMap[preference]
|
||||
content = "%s=%s&hasChanged=1" % (formKey, urllib.quote(value))
|
||||
|
||||
def get_settings(self, preference=None):
|
||||
return self._get('jsonSettings', preference)
|
||||
|
||||
def set_nosave(self, preference, value=None):
|
||||
# First check if we did a get, if not, must get first
|
||||
if not self.preferences:
|
||||
self.load_preferences()
|
||||
url = "/SOGo/so/%s/preferences" % self.login
|
||||
|
||||
# Get the right sub-dict and change the key/value
|
||||
subdict = self.find_key(self.preferences, preference)
|
||||
if not subdict:
|
||||
print "LUC(nosubdict):", preference, self.preferences
|
||||
subdict[preference] = value
|
||||
post = HTTPPreferencesPOST (url, content)
|
||||
post.content_type = "application/x-www-form-urlencoded"
|
||||
post.cookie = self.cookie
|
||||
|
||||
def set(self, preference, value=None):
|
||||
self.set_nosave(preference, value)
|
||||
self.save()
|
||||
self.client.execute (post)
|
||||
|
||||
def set_multiple(self, preferences={}):
|
||||
for key, value in preferences.iteritems():
|
||||
self.set_nosave(key, value)
|
||||
self.save()
|
||||
|
||||
def set_or_create(self, preference, value, paths=['defaults']):
|
||||
if not self.preferences:
|
||||
self.load_preferences()
|
||||
subdict = self.find_key(self.preferences, preference)
|
||||
#- Pref is not set
|
||||
if not subdict:
|
||||
subdict = self.preferences
|
||||
for path in paths:
|
||||
subdict = subdict.setdefault(path, {})
|
||||
subdict[preference] = value
|
||||
|
||||
def save(self):
|
||||
url = "/SOGo/so/%s/Preferences/save" % self.login
|
||||
|
||||
post = HTTPPreferencesPOST(url, simplejson.dumps(self.preferences))
|
||||
post.content_type = "application/json"
|
||||
post.cookie = self.cookie
|
||||
self.client.execute(post)
|
||||
|
||||
# Raise an exception if the pref wasn't properly set
|
||||
if post.response["status"] != 200:
|
||||
raise Exception ("failure setting prefs, (code = %d)" \
|
||||
# Raise an exception if the pref wasn't properly set
|
||||
if post.response["status"] != 200:
|
||||
raise Exception ("failure setting prefs, (code = %d)" \
|
||||
% post.response["status"])
|
||||
|
||||
def get(self, preference=None):
|
||||
url = "/SOGo/so/%s/preferences/jsonDefaults" % self.login
|
||||
get = HTTPPreferencesGET (url)
|
||||
get.cookie = self.cookie
|
||||
self.client.execute (get)
|
||||
content = simplejson.loads(get.response['body'])
|
||||
result = None
|
||||
try:
|
||||
if preference:
|
||||
result = content[preference]
|
||||
else:
|
||||
result = content
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
def get_settings(self, preference=None):
|
||||
url = "/SOGo/so/%s/preferences/jsonSettings" % self.login
|
||||
get = HTTPPreferencesGET (url)
|
||||
get.cookie = self.cookie
|
||||
self.client.execute (get)
|
||||
content = simplejson.loads(get.response['body'])
|
||||
result = None
|
||||
try:
|
||||
if preference:
|
||||
result = content[preference]
|
||||
else:
|
||||
result = content
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
# Simple main to test this class
|
||||
if __name__ == "__main__":
|
||||
p = preferences ()
|
||||
print p.get ("SOGoLanguage")
|
||||
p.set ("SOGoLanguage", SOGoSupportedLanguages.index("French"))
|
||||
print p.get ("SOGoLanguage")
|
||||
p = preferences ()
|
||||
print p.get ("SOGoLanguage")
|
||||
p.set ("SOGoLanguage", SOGoSupportedLanguages.index("French"))
|
||||
print p.get ("SOGoLanguage")
|
||||
@@ -14,9 +14,8 @@ def getAuthCookie(hostname, port, username, password) :
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cjar))
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
creds2 = simplejson.dumps({"userName":username, "password": password})
|
||||
req = urllib2.Request("http://%s:%s/SOGo/connect" % (hostname, port), creds2,
|
||||
{'Content-Type': 'application/json'})
|
||||
creds = urllib.urlencode([("userName",username), ("password", password)])
|
||||
req = urllib2.Request("http://%s:%s/SOGo/connect" % (hostname, port), creds)
|
||||
|
||||
fd = urllib2.urlopen(req)
|
||||
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
|
||||
from config import hostname, port, username, password
|
||||
|
||||
import carddav
|
||||
import sogotests
|
||||
import unittest
|
||||
import webdavlib
|
||||
import time
|
||||
|
||||
|
||||
class JsonDavEventTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._connect_as_user()
|
||||
|
||||
def _connect_as_user(self, newuser=username, newpassword=password):
|
||||
self.dv = carddav.Carddav(newuser, newpassword)
|
||||
|
||||
def _create_new_event(self, path):
|
||||
gid = self.dv.newguid(path)
|
||||
event = {'startDate': "2015-12-25",
|
||||
'startTime': "10:00",
|
||||
'endDate': "2015-12-25",
|
||||
'endTime': "23:00",
|
||||
'isTransparent': 0,
|
||||
'sendAppointmentNotifications': 0,
|
||||
'summary': "Big party",
|
||||
'alarm': {'action': 'display',
|
||||
'quantity': 10,
|
||||
'unit': "MINUTES",
|
||||
'reference': "BEFORE",
|
||||
'relation': "START",
|
||||
'email': "sogo1@example.com"},
|
||||
'organizer': {'name': u"Balthazar C\xe9sar",
|
||||
'email': "sogo2@example.com"},
|
||||
'c_name': gid,
|
||||
'c_folder': path
|
||||
}
|
||||
return (event, path, gid)
|
||||
|
||||
def _get_dav_data(self, filename, user=username, passwd=password):
|
||||
w = webdavlib.WebDAVClient(hostname, port, user, passwd)
|
||||
query = webdavlib.HTTPGET("http://localhost/SOGo/dav/%s/Calendar/personal/%s" % (username, filename))
|
||||
w.execute(query)
|
||||
self.assertEquals(query.response['status'], 200)
|
||||
return query.response['body'].split("\r\n")
|
||||
|
||||
def _get_dav_field(self, davdata, fieldname):
|
||||
try:
|
||||
data = [a.split(':')[1] for a in davdata if fieldname in a][0]
|
||||
except IndexError:
|
||||
data = ''
|
||||
return data
|
||||
|
||||
def test_create_new_event(self):
|
||||
path = 'Calendar/personal'
|
||||
(event, folder, gid) = self._create_new_event(path)
|
||||
#print "Saving Event to:", folder, gid
|
||||
self.dv.save_event(event, folder, gid)
|
||||
#- Get the event back with JSON
|
||||
self._connect_as_user()
|
||||
self.dv.load_events()
|
||||
elist = [e for e in self.dv.events if e['c_name'] == gid]
|
||||
#- MUST have this event -- only once
|
||||
self.assertEquals(len(elist), 1)
|
||||
strdate = "%d-%.02d-%.02d" % time.gmtime(elist[0]['c_startdate'])[0:3]
|
||||
self.assertEquals(strdate, event['startDate'])
|
||||
#- Get the event back with DAV
|
||||
dav = self._get_dav_data(gid, username, password)
|
||||
self.assertEquals(self._get_dav_field(dav, 'SUMMARY:'), event['summary'])
|
||||
|
||||
|
||||
class JsonDavPhoneTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._connect_as_user()
|
||||
self.newphone = [{'type': 'home', 'value': '123.456.7890'}]
|
||||
self.newphones_difftype = [{'type': 'home', 'value': '123.456.7890'},
|
||||
{'type': 'work', 'value': '987.654.3210'},
|
||||
{'type': 'fax', 'value': '555.666.7777'}]
|
||||
self.newphones_sametype = [{'type': 'work', 'value': '123.456.7890'},
|
||||
{'type': 'work', 'value': '987.654.3210'}]
|
||||
# Easier to erase them all in tearDown
|
||||
self.allphones = list(self.newphone)
|
||||
self.allphones.extend(self.newphones_difftype)
|
||||
self.allphones.extend(self.newphones_sametype)
|
||||
|
||||
def tearDown(self):
|
||||
self._connect_as_user()
|
||||
self._get_card()
|
||||
#- Remove the phones we just added
|
||||
for phone in self.allphones:
|
||||
try:
|
||||
self.card['phones'].pop(self.card['phones'].index(phone))
|
||||
except ValueError:
|
||||
#print "Can't find", phone
|
||||
pass
|
||||
self._save_card()
|
||||
|
||||
|
||||
def _connect_as_user(self, newuser=username, newpassword=password):
|
||||
self.dv = carddav.Carddav(newuser, newpassword)
|
||||
|
||||
def _get_card(self, name="John Doe"):
|
||||
tmp_card = self.dv.get_cards(name)[0]
|
||||
self.card = self.dv.get_card(tmp_card['c_name'])
|
||||
|
||||
def _save_card(self, card=None):
|
||||
if card:
|
||||
self.dv.save_card(card)
|
||||
else:
|
||||
self.dv.save_card(self.card)
|
||||
|
||||
def _get_dav_data(self, filename, user=username, passwd=password):
|
||||
w = webdavlib.WebDAVClient(hostname, port, user, passwd)
|
||||
query = webdavlib.HTTPGET("http://localhost/SOGo/dav/%s/Contacts/personal/%s" % (username, filename))
|
||||
w.execute(query)
|
||||
self.assertEquals(query.response['status'], 200)
|
||||
return query.response['body'].split("\r\n")
|
||||
|
||||
def _phone_to_dav_str(self, phonedict):
|
||||
return "TEL;TYPE=%s:%s" % (phonedict['type'], phonedict['value'])
|
||||
|
||||
def _testMultiplePhones(self, phones):
|
||||
""" Add Multiple Phones to Contact JSON and verify with DAV """
|
||||
#- Use JSON to get CARD and add a phone and save it back
|
||||
self._get_card()
|
||||
oldphones = self.card['phones']
|
||||
oldphones.extend(phones)
|
||||
self._save_card()
|
||||
#- Make sure that the phone is there when using JSON
|
||||
self._connect_as_user()
|
||||
self._get_card()
|
||||
#print "C:::", self.card
|
||||
testphones = self.card['phones']
|
||||
#print "P1:", oldphones
|
||||
#print "P2:", testphones
|
||||
self.assertEquals(sorted(oldphones), sorted(testphones))
|
||||
#- Verify that DAV has the same values
|
||||
dav = self._get_dav_data(self.card['id'], username, password)
|
||||
for phone in phones:
|
||||
found = dav.index(self._phone_to_dav_str(phone))
|
||||
self.assertTrue(found > 0)
|
||||
|
||||
def testSinglePhone(self):
|
||||
self._testMultiplePhones(self.newphone)
|
||||
|
||||
def testMultipleDifferentPhones(self):
|
||||
self._testMultiplePhones(self.newphones_difftype)
|
||||
|
||||
def testMultipleSameTypePhones(self):
|
||||
self._testMultiplePhones(self.newphones_sametype)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sogotests.runTests()
|
||||
@@ -9,17 +9,6 @@ import utilities
|
||||
|
||||
class preferencesTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.prefs = preferences.preferences()
|
||||
# because if not set in vacation will not be found later
|
||||
# we must make sure they are there at the start
|
||||
self.prefs.set_or_create("autoReplyText", '', ["defaults", "Vacation"])
|
||||
self.prefs.set_or_create("PreventInvitations", 0, ["settings", "Calendar"])
|
||||
self.prefs.set_or_create("PreventInvitationsWhitelist", [], ["settings", "Calendar"])
|
||||
|
||||
def tearDown(self):
|
||||
self.prefs.set("autoReplyText", "")
|
||||
|
||||
def _setTextPref(self, prefText = None ):
|
||||
""" set a text preference to a known value """
|
||||
self.prefs.set("autoReplyText", prefText)
|
||||
@@ -30,8 +19,15 @@ class preferencesTest(unittest.TestCase):
|
||||
self.assertEqual(prefData["autoReplyText"], prefText,
|
||||
"%s != %s" % (prefData["autoReplyText"], prefText))
|
||||
|
||||
def setUp(self):
|
||||
self.prefs = preferences.preferences()
|
||||
|
||||
def tearDown(self):
|
||||
self.prefs.set("autoReplyText", "")
|
||||
|
||||
def testSetTextPreferences(self):
|
||||
""" Set/get a text preference - normal characters"""
|
||||
|
||||
self._setTextPref("defaultText")
|
||||
|
||||
def testSetTextPreferencesWeirdChars(self):
|
||||
@@ -41,17 +37,17 @@ class preferencesTest(unittest.TestCase):
|
||||
|
||||
def testSetPreventInvitation(self):
|
||||
""" Set/get the PreventInvitation pref"""
|
||||
self.prefs.set('PreventInvitations', 0)
|
||||
notset = self.prefs.get('Calendar')['PreventInvitations']
|
||||
self.prefs.set('PreventInvitations', '0')
|
||||
notset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
||||
self.assertEqual(notset, 0)
|
||||
self.prefs.set('PreventInvitations', 1)
|
||||
isset = self.prefs.get('Calendar')['PreventInvitations']
|
||||
self.prefs.set('enablePreventInvitations', '0')
|
||||
isset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
||||
self.assertEqual(isset, 1)
|
||||
|
||||
def testPreventInvitationsWhiteList(self):
|
||||
"""Add to the PreventInvitations Whitelist"""
|
||||
self.prefs.set("PreventInvitationsWhitelist", white_listed_attendee)
|
||||
whitelist = self.prefs.get('Calendar')['PreventInvitationsWhitelist']
|
||||
self.prefs.set("whiteList", simplejson.dumps(white_listed_attendee))
|
||||
whitelist = self.prefs.get_settings('Calendar')['PreventInvitationsWhitelist']
|
||||
self.assertEqual(whitelist, white_listed_attendee)
|
||||
|
||||
|
||||
|
||||
@@ -26,26 +26,18 @@ class preventInvitationsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.prefs = preferences.preferences(attendee1, attendee1_password)
|
||||
self.caldav = CalDAVSchedulingTest(self)
|
||||
cal = self.prefs.get("Calendar")
|
||||
if "PreventInvitationsWhitelist" not in cal:
|
||||
#print "LUC (cal):", cal
|
||||
cal["PreventInvitationsWhitelist"] = None
|
||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
||||
|
||||
def tearDown(self):
|
||||
self.prefs.set("autoReplyText", "")
|
||||
self.prefs.set('PreventInvitations', 0)
|
||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
||||
self.prefs.set('PreventInvitations', '0')
|
||||
self.prefs.set("whiteList", "")
|
||||
#- Manual Cleanup, not called because classs is not derived from unittest
|
||||
self.caldav.tearDown()
|
||||
|
||||
def not_test_empty_string_instead_of_null(self):
|
||||
self.prefs.set('PreventInvitationsWhitelist', "")
|
||||
|
||||
def testDontPreventInvitation(self):
|
||||
""" Set/get the PreventInvitation pref"""
|
||||
#- First accept the invitation
|
||||
self.prefs.set('PreventInvitations', 0)
|
||||
self.prefs.set('PreventInvitations', '0')
|
||||
notset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
||||
self.assertEqual(notset, 0)
|
||||
self.caldav.AddAttendee()
|
||||
@@ -54,7 +46,7 @@ class preventInvitationsTest(unittest.TestCase):
|
||||
def testPreventInvitation(self):
|
||||
""" Set PreventInvitation and don't accept the Invitation"""
|
||||
#- Second, enable PreventInviation and refuse it
|
||||
self.prefs.set('PreventInvitations', 1)
|
||||
self.prefs.set('enablePreventInvitations', '0')
|
||||
isset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
||||
self.assertEqual(isset, 1)
|
||||
self.caldav.AddAttendee(409)
|
||||
@@ -63,13 +55,13 @@ class preventInvitationsTest(unittest.TestCase):
|
||||
def testPreventInvitationWhiteList(self):
|
||||
""" Set PreventInvitation add to WhiteList and accept the Invitation"""
|
||||
#- First, add the Organiser to the Attendee's whitelist
|
||||
self.prefs.set('PreventInvitations', 1)
|
||||
self.prefs.set("PreventInvitationsWhitelist", simplejson.dumps(white_listed_attendee))
|
||||
self.prefs.set('enablePreventInvitations', '0')
|
||||
self.prefs.set("whiteList", simplejson.dumps(white_listed_attendee))
|
||||
whitelist = self.prefs.get_settings('Calendar')['PreventInvitationsWhitelist']
|
||||
self.assertEqual(whitelist, white_listed_attendee)
|
||||
|
||||
#- Second, try again to invite, it should work
|
||||
self.prefs.set('PreventInvitations', 1)
|
||||
self.prefs.set('enablePreventInvitations', '0')
|
||||
isset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
||||
self.assertEqual(isset, 1)
|
||||
self.caldav.AddAttendee()
|
||||
|
||||
Executable → Regular
+34
-44
@@ -16,18 +16,17 @@ sieve_simple_filter="""require ["fileinto"];\r\nif anyof (header :contains "subj
|
||||
|
||||
class sieveTest(unittest.TestCase):
|
||||
def _killFilters(self):
|
||||
self.prefs=preferences.preferences()
|
||||
filtersKill={}
|
||||
# kill existing filters
|
||||
self.prefs.set_or_create("SOGoSieveFilters", [], ["defaults"])
|
||||
filtersKill["SOGoSieveFilters"]= "[]"
|
||||
# vacation filters
|
||||
self.prefs.set_or_create("autoReplyText", "", ["defaults", "Vacation"])
|
||||
self.prefs.set_or_create("autoReplyEmailAddresses", [], ["defaults", "Vacation"])
|
||||
self.prefs.set_or_create("daysBetweenResponse", 7, ["defaults", "Vacation"])
|
||||
self.prefs.set_or_create("ignoreLists", 0, ["defaults", "Vacation"])
|
||||
self.prefs.set_or_create("enabled", 0, ["defaults", "Vacation"])
|
||||
filtersKill["autoReplyText"] = ""
|
||||
filtersKill["autoReplyEmailAddresses"] = ""
|
||||
# forwarding filters
|
||||
self.prefs.set_or_create("forwardAddress", [], ["defaults", "Forward"])
|
||||
self.prefs.set_or_create("keepCopy", 0, ["defaults", "Forward"])
|
||||
filtersKill["forwardAddress"] = ""
|
||||
|
||||
self.prefs=preferences.preferences()
|
||||
self.prefs.set(filtersKill)
|
||||
|
||||
def setUp(self):
|
||||
ret = ""
|
||||
@@ -55,7 +54,7 @@ class sieveTest(unittest.TestCase):
|
||||
|
||||
for (script, isActive) in sieveScriptList:
|
||||
if (script == "sogo"):
|
||||
sieveFoundsogo=1
|
||||
sieveFoundsogo=1
|
||||
self.assertEqual(isActive, True, "sogo sieve script is not active!")
|
||||
(ret, createdSieveScript) = self.ms.getscript(script)
|
||||
self.assertEqual(ret, "OK", "Couldn't get sogo sieve script")
|
||||
@@ -74,16 +73,13 @@ class sieveTest(unittest.TestCase):
|
||||
"days": preferences.daysBetweenResponseList[daysSelect],
|
||||
}
|
||||
|
||||
# Enabling Vacation now is an 'enabled' setting in the subdict Vacation
|
||||
# We need to get that subdict first -- next save/set will also save this
|
||||
vacation = self.prefs.get("Vacation")
|
||||
vacation['enabled'] = 1
|
||||
|
||||
self.prefs.set_nosave("autoReplyText", vacation_msg)
|
||||
self.prefs.set_nosave("daysBetweenResponse", "%d" % preferences.daysBetweenResponseList[daysSelect])
|
||||
self.prefs.set_nosave("autoReplyEmailAddresses", [self.user_email])
|
||||
self.prefs.save()
|
||||
filterAdd = {"Vacation":"1",
|
||||
"autoReplyText": vacation_msg,
|
||||
"daysBetweenResponse": "%d" % daysSelect,
|
||||
"autoReplyEmailAddresses": self.user_email,
|
||||
}
|
||||
|
||||
self.prefs.set(filterAdd)
|
||||
createdSieveScript=self._getSogoSieveScript()
|
||||
|
||||
self.assertEqual(sieveScript, createdSieveScript)
|
||||
@@ -98,17 +94,14 @@ class sieveTest(unittest.TestCase):
|
||||
"days": preferences.daysBetweenResponseList[daysSelect],
|
||||
}
|
||||
|
||||
# Enabling Vacation now is an 'enabled' setting in the subdict Vacation
|
||||
# We need to get that subdict first -- next save/set will also save this
|
||||
vacation = self.prefs.get("Vacation")
|
||||
vacation['enabled'] = 1
|
||||
|
||||
self.prefs.set_nosave("autoReplyText", vacation_msg)
|
||||
self.prefs.set_nosave("daysBetweenResponse", "%d" % preferences.daysBetweenResponseList[daysSelect])
|
||||
self.prefs.set_nosave("autoReplyEmailAddresses", [self.user_email])
|
||||
self.prefs.set_nosave("ignoreLists", 1)
|
||||
self.prefs.save()
|
||||
filterAdd = {"Vacation":"1",
|
||||
"autoReplyText": vacation_msg,
|
||||
"daysBetweenResponse": "%d" % daysSelect,
|
||||
"autoReplyEmailAddresses": self.user_email,
|
||||
"ignoreLists": "1",
|
||||
}
|
||||
|
||||
self.prefs.set(filterAdd)
|
||||
createdSieveScript=self._getSogoSieveScript()
|
||||
|
||||
self.assertEqual(sieveScript, createdSieveScript)
|
||||
@@ -119,13 +112,11 @@ class sieveTest(unittest.TestCase):
|
||||
|
||||
sieveScript = sieve_simple_forward % { "redirect_mailaddr": redirect_mailaddr }
|
||||
|
||||
# Enabling Forward now is an 'enabled' setting in the subdict Forward
|
||||
# We need to get that subdict first -- next save/set will also save this
|
||||
forward = self.prefs.get("Forward")
|
||||
forward['enabled'] = 1
|
||||
|
||||
self.prefs.set("forwardAddress", [redirect_mailaddr])
|
||||
filterAdd = { "Forward": "1",
|
||||
"forwardAddress": redirect_mailaddr,
|
||||
}
|
||||
|
||||
self.prefs.set(filterAdd)
|
||||
createdSieveScript = self._getSogoSieveScript()
|
||||
self.assertEqual(sieveScript, createdSieveScript)
|
||||
|
||||
@@ -135,15 +126,12 @@ class sieveTest(unittest.TestCase):
|
||||
|
||||
sieveScript = sieve_forward_keep % { "redirect_mailaddr": redirect_mailaddr }
|
||||
|
||||
# Enabling Forward now is an 'enabled' setting in the subdict Forward
|
||||
# We need to get that subdict first -- next save/set will also save this
|
||||
forward = self.prefs.get("Forward")
|
||||
forward['enabled'] = 1
|
||||
|
||||
self.prefs.set_nosave("forwardAddress", [redirect_mailaddr])
|
||||
self.prefs.set_nosave("keepCopy", 1)
|
||||
self.prefs.save()
|
||||
filterAdd = { "Forward": "1",
|
||||
"forwardAddress": redirect_mailaddr,
|
||||
"keepCopy": "1",
|
||||
}
|
||||
|
||||
self.prefs.set(filterAdd)
|
||||
createdSieveScript = self._getSogoSieveScript()
|
||||
self.assertEqual(sieveScript, createdSieveScript)
|
||||
|
||||
@@ -154,8 +142,10 @@ class sieveTest(unittest.TestCase):
|
||||
|
||||
sieveScript=sieve_simple_filter % { "subject": subject, "folderName": folderName }
|
||||
|
||||
self.prefs.set("SOGoSieveFilters", [{"active": True, "actions": [{"method": "fileinto", "argument": "Sent"}], "rules": [{"operator": "contains", "field": "subject", "value": subject}], "match": "any", "name": folderName}])
|
||||
filterAdd = { "SOGoSieveFilters": """[{"active": true, "actions": [{"method": "fileinto", "argument": "Sent"}], "rules": [{"operator": "contains", "field": "subject", "value": "%s"}], "match": "any", "name": "%s"}]""" % (subject, folderName)
|
||||
}
|
||||
|
||||
self.prefs.set(filterAdd)
|
||||
createdSieveScript = self._getSogoSieveScript()
|
||||
self.assertEqual(sieveScript, createdSieveScript)
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ class WebDAVClient:
|
||||
query.set_response(self.conn.getresponse());
|
||||
except httplib.BadStatusLine, e:
|
||||
print e
|
||||
time.sleep(3)
|
||||
query.set_response(self.conn.getresponse());
|
||||
query.duration = time.time() - query.start
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ $(TEST_TOOL)_OBJC_FILES += \
|
||||
TestSBJsonParser.m \
|
||||
\
|
||||
TestNGMimeAddressHeaderFieldGenerator.m \
|
||||
TestNGMimeMessageGenerator.m \
|
||||
\
|
||||
TestNSData+Crypto.m \
|
||||
TestNSString+Crypto.m \
|
||||
TestNSString+URLEscaping.m \
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
@class NSArray;
|
||||
@@ -52,6 +54,8 @@
|
||||
|
||||
- (BOOL) run;
|
||||
|
||||
- (NSString*) stringFromDiffBetween: (NSString*) str1
|
||||
and: (NSString*) str2;
|
||||
@end
|
||||
|
||||
#define test(c) { \
|
||||
|
||||
@@ -185,4 +185,79 @@ static NSString *SOGoTestAssertException = @"SOGoTestAssertException";
|
||||
return YES;
|
||||
}
|
||||
|
||||
/* Helper function for diffForString:andString */
|
||||
NSString *_stringForCharacterAtIndex(NSUInteger index, NSString *str, NSUInteger length)
|
||||
{
|
||||
NSString *chrStr;
|
||||
unichar chr;
|
||||
if (index < length)
|
||||
{
|
||||
chr = [str characterAtIndex: index];
|
||||
if (isprint(chr))
|
||||
{
|
||||
chrStr = [NSString stringWithFormat: @"%c", chr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chr == 10)
|
||||
chrStr = @"[NL]";
|
||||
else if (chr == 0)
|
||||
chrStr = @"[\0]";
|
||||
else
|
||||
chrStr = [NSString stringWithFormat: @"[NP: %u]", chr];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
chrStr = @"[none]";
|
||||
}
|
||||
|
||||
return chrStr;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a string with a very verbose diff of the two strings.
|
||||
In case the strings are equal it returns an empty string.
|
||||
Example output for the strings 'flower' and 'flotera':
|
||||
<begin of example>
|
||||
0 |f|
|
||||
1 |l|
|
||||
2 |o|
|
||||
3 |w|t|<--
|
||||
4 |e|
|
||||
5 |r|
|
||||
6 |[none]|a|<--
|
||||
<end of example>
|
||||
*/
|
||||
- (NSString*) stringFromDiffBetween: (NSString*) str1
|
||||
and: (NSString*) str2
|
||||
{
|
||||
BOOL differencesFound = NO;
|
||||
NSString *finalSTR = @"";
|
||||
NSUInteger i, length1, length2;
|
||||
NSString *sc1, *sc2;
|
||||
|
||||
length1 = [str1 length];
|
||||
length2 = [str2 length];
|
||||
for (i = 0; i < length1 || i < length2; i++)
|
||||
{
|
||||
sc1 = _stringForCharacterAtIndex(i, str1, length1);
|
||||
sc2 = _stringForCharacterAtIndex(i, str2, length2);
|
||||
|
||||
if ([sc1 isEqualToString: sc2])
|
||||
finalSTR = [finalSTR stringByAppendingFormat: @"%u |%@|\n", i, sc1];
|
||||
else
|
||||
{
|
||||
finalSTR = [finalSTR stringByAppendingFormat: @"%u |%@|%@|<--\n", i, sc1, sc2];
|
||||
differencesFound = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!differencesFound)
|
||||
return @"";
|
||||
|
||||
return finalSTR;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,134 @@
|
||||
/* TestNGMimeMessageGenerator.m - this file is part of SOGo
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import "SOGoTest.h"
|
||||
#import <NGMail/NGMimeMessageGenerator.h>
|
||||
|
||||
@interface TestNGMimeMessageGenerator : SOGoTest
|
||||
@end
|
||||
|
||||
@implementation TestNGMimeMessageGenerator
|
||||
|
||||
- (void) test_generateDataForHeaderField_value
|
||||
{
|
||||
NGMimeMessageGenerator *generator;
|
||||
NSArray *cases = [NSArray arrayWithObjects:
|
||||
[NSArray arrayWithObjects: @"Message-ID", @"<CADCKkzo+9X1SniJFY3yc7YGafNrmAts419RmcqNkMzd-PBqNbA@mail.gmail.com>", @"<CADCKkzo+9X1SniJFY3yc7YGafNrmAts419RmcqNkMzd-PBqNbA@mail.gmail.com>", nil],
|
||||
[NSArray arrayWithObjects: @"Content-Type",
|
||||
@"text/plain; charset=utf-8; format=flowed",
|
||||
@"text/plain; charset=utf-8; format=flowed",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneHebrewOneLatin",
|
||||
@"עs",
|
||||
@"=?utf-8?q?=D7=A2s?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneLatineOneHebrew",
|
||||
@"sע",
|
||||
@"=?utf-8?q?s=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneCharacterHebrew",
|
||||
@"ע",
|
||||
@"=?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneCharacterRussian",
|
||||
@"Б",
|
||||
@"=?utf-8?q?=D0=91?=",
|
||||
nil],
|
||||
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderParameter",
|
||||
@"parameter=ע",
|
||||
@"parameter==?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderParameters",
|
||||
@"plain; parameter=ע; parameter-plain; parameter2=ea",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=;\n parameter-plain; parameter2=ea",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderAndNoParameter",
|
||||
@"plain; parameter=ע; parameter-plain; ע",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=; parameter-plain;\n =?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderAndTwoParameter",
|
||||
@"plain; parameter=ע; parameter-plain; z=ע",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=; parameter-plain;\n z==?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderExtrablanks",
|
||||
@"plain; parameter=ע; parameter 2spaces; parameter2=ea",
|
||||
@"plain;\n \\ parameter==?utf-8?q?=D7=A2?=;\n parameter 2spaces; parameter2=ea",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-Encoded-Unbalanced-Paramter-Quote",
|
||||
@"text/plain; name=\"ע",
|
||||
@"text/plain;\n name==?utf-8?q?=22=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-type",
|
||||
@"text/plain; name=\"АБВГДЕЁЖЗИЙ, КЛМНОПРСТУФ y ЦЧШЩЪЫЬЭЮЯ.txt\"",
|
||||
@"text/plain;\n name=\"=?utf-8?q?=D0=90=D0=91=D0=92=D0=93=D0=94=D0=95=D0=81=D0=96=D0=97=D0=98=D0=99=2C_=D0=9A=D0=9B=D0=9C=D0=9D=D0=9E=D0=9F=D0=A0=D0=A1=D0=A2=D0=A3=D0=A4_y_=D0=A6=D0=A7=D0=A8=D0=A9=D0=AA=D0=AB=D0=AC=D0=AD=D0=AE=D0=AF=2Etxt?=\"",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-disposition",
|
||||
@"attachment; filename=\"АБВГДЕЁЖЗИЙ, КЛМНОПРСТУФ y ЦЧШЩЪЫЬЭЮЯ.txt\"",
|
||||
@"attachment;\n filename=\"=?utf-8?q?=D0=90=D0=91=D0=92=D0=93=D0=94=D0=95=D0=81=D0=96=D0=97=D0=98=D0=99=2C_=D0=9A=D0=9B=D0=9C=D0=9D=D0=9E=D0=9F=D0=A0=D0=A1=D0=A2=D0=A3=D0=A4_y_=D0=A6=D0=A7=D0=A8=D0=A9=D0=AA=D0=AB=D0=AC=D0=AD=D0=AE=D0=AF=2Etxt?=\"",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-length", @"2912", @"2912", nil],
|
||||
[NSArray arrayWithObjects: @"content-transfer-encoding", @"quoted-printable", @"quoted-printable", nil],
|
||||
nil
|
||||
];
|
||||
NSEnumerator *enumerator;
|
||||
NSArray *testCase;
|
||||
|
||||
[NGMimeMessageGenerator initialize];
|
||||
generator = [[NGMimeMessageGenerator alloc] init];
|
||||
[generator autorelease];
|
||||
|
||||
enumerator = [cases objectEnumerator];
|
||||
while ((testCase = [enumerator nextObject]) != nil)
|
||||
{
|
||||
NSData *result;
|
||||
NSMutableData *resultWithNulByte;
|
||||
NSString *header = [testCase objectAtIndex: 0];
|
||||
NSData *headerData = [testCase objectAtIndex: 1];
|
||||
NSString *expected = [testCase objectAtIndex: 2];
|
||||
result = [generator generateDataForHeaderField: header
|
||||
value: headerData];
|
||||
if (result == nil)
|
||||
result = [@"[nil]" dataUsingEncoding: NSUTF8StringEncoding];
|
||||
|
||||
resultWithNulByte = [result mutableCopy];
|
||||
[resultWithNulByte appendBytes: "\0" length: 1];
|
||||
NSString *resultString = [NSString stringWithCString:[resultWithNulByte bytes]];
|
||||
|
||||
|
||||
BOOL testResult = [resultString isEqualToString: expected];
|
||||
|
||||
NSString *diff = [self stringFromDiffBetween: [NSString stringWithString: resultString]
|
||||
and: [NSString stringWithString: expected]];
|
||||
NSString *testErrorMsg = [NSString
|
||||
stringWithFormat: @">> For %@ header received:\n%@[END]\n>> instead of:\n%@[END]\n>> for:\n%@\n>> diff:\n%@\n>> lengthReceived: %u lengthExpected: %u",
|
||||
header,
|
||||
resultString,
|
||||
expected,
|
||||
headerData,
|
||||
diff,
|
||||
[resultString length],
|
||||
[expected length]
|
||||
];
|
||||
|
||||
testWithMessage(testResult, testErrorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Loaded 100 of 1067 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user