Compare commits

...
293 Commits
Author SHA1 Message Date
Ludovic Marcotte 29f2d960a7 Enable old code. 2015-12-15 16:15:48 -05:00
Ludovic Marcotte 225fe847a7 Disabled today's code for now. It requires more testing. 2015-12-15 16:12:17 -05:00
Ludovic Marcotte 720b1d5ab5 (fix) carry over delegatedFrom too 2015-12-15 15:21:33 -05:00
Ludovic Marcotte 28bd4ca318 (fix) carry over the delegatedTo, not only the part stat 2015-12-15 15:05:40 -05:00
Francis Lachapelle c6dbcb2f7c Update ChangeLog 2015-12-15 14:24:25 -05:00
Francis Lachapelle 5dfbfe827d Preparation for release 2.3.4 2015-12-15 14:24:12 -05:00
Francis Lachapelle 6ddb40fd2b Update CKEditor to version 4.5.6 2015-12-15 14:20:57 -05:00
Francis Lachapelle 37f29c06ea Localization 2015-12-15 13:18:06 -05:00
Ludovic Marcotte 248892c394 (fix) small fix over previous commit 2015-12-15 11:14:58 -05:00
Ludovic Marcotte b4e4b9c29f (fix) avoid duplicating attendees when accepting event using a different identity over CalDAV 2015-12-15 11:08:59 -05:00
Francis Lachapelle e94b4416f8 Localization cleanup 2015-12-15 10:05:21 -05:00
Francis Lachapelle 8d19ec4aff Remove duplicate key in MainUI localization 2015-12-15 09:55:51 -05:00
Ludovic Marcotte 980db7a980 (fix #127) fallback to "work" when export as ldif 2015-12-11 14:51:08 -05:00
Ludovic Marcotte c5c6ff33d8 (fix #119) disable servername and url by default 2015-12-11 14:11:26 -05:00
Ludovic Marcotte 7e1bbe34dc (fix #118) handle /.well-known/carddav 2015-12-11 14:03:50 -05:00
Ludovic Marcotte f9884e3f55 (fix) added new strings from v3 2015-12-10 14:12:33 -05:00
Francis Lachapelle 15ab0f851d Localization 2015-12-08 10:05:19 -05:00
Ludovic Marcotte 1f98d7cced (fix) fixed broken translations 2015-12-03 15:40:38 -05:00
Ludovic Marcotte 2755a83e65 (fix) remove trailing semicolon everywhere 2015-12-03 15:02:01 -05:00
Francis Lachapelle 9936477f07 Update translations 2015-12-03 13:09:24 -05:00
Ludovic Marcotte 8c0ef9cf50 (fix) proper fix for #3389 2015-12-01 11:42:02 -05:00
Ludovic Marcotte 8456d49686 Revert "(fix) fixed addressbrook-only source entires having a c_uid set"
This reverts commit ef7de7c10d.
2015-12-01 11:39:07 -05:00
Ludovic Marcotte 5a3cafbbb0 (fix) prevent characters in calendar component UID causing issues during import process 2015-12-01 09:26:52 -05:00
Ludovic Marcotte dc085789e7 (feat) Initial support for EAS calendar exceptions 2015-11-30 09:19:01 -05:00
Francis Lachapelle bbdb27e75e (css) Enlarge max width of toolbar buttons 2015-11-27 08:36:34 -05:00
Ludovic Marcotte 1279629e86 (fix) correctly handle all-day event exceptions when the master event changes 2015-11-25 15:53:08 -05:00
Ludovic Marcotte f7d748ea98 (fix) EAS fix on qp-encoded subjects (#3390) 2015-11-25 15:08:24 -05:00
Ludovic Marcotte 2d1a4b320c (fix) adjust doc and packaging regarding oc cleanup script 2015-11-25 10:56:39 -05:00
Ludovic Marcotte 8df311547f (fix) EAS fix for wrong charset being used (#3392) 2015-11-23 18:07:50 -05:00
Francis Lachapelle f42d6f5f4a Enlarge size of event/task editor window
Fixes #3381
2015-11-23 13:29:02 -05:00
Ludovic Marcotte 01642eca3e (fix) fixed one unit test 2015-11-23 13:20:55 -05:00
Francis Lachapelle 2ae8c6f8b2 Add missing tooltips on buttons of toolbars 2015-11-23 10:33:03 -05:00
Ludovic Marcotte 31deae11b4 (fix) domain in doc 2015-11-20 11:21:38 -05:00
extrafu 19676593ea (fix) allow getting password from context for OC 2015-11-19 17:57:04 -05:00
Francis Lachapelle 4a27beabdd (css) Limit toolbar buttons min/max width 2015-11-19 09:46:07 -05:00
Francis Lachapelle 8dae5faaa1 Localization 2015-11-19 09:41:36 -05:00
Francis Lachapelle 85ce41dc68 (fix) JS exception when printing calendars
Fixes #3203
2015-11-19 09:23:46 -05:00
Francis Lachapelle 7015713741 Update ChangeLog 2015-11-18 09:43:02 -05:00
Francis Lachapelle 7644d6fb4d Preparation for release 2.3.3a 2015-11-18 09:42:05 -05:00
Francis Lachapelle 4b8a9bcc3b Always show user's name in top banner 2015-11-16 09:02:37 -05:00
Francis Lachapelle a9984d9487 Fix regression when saving mail folders state 2015-11-16 09:01:05 -05:00
Francis Lachapelle 2a90102a81 Update translations 2015-11-11 15:51:33 -05:00
Francis Lachapelle fbc73dc3bc Update ChangeLog 2015-11-11 14:06:11 -05:00
Francis Lachapelle 1d78b2df96 Preparation for release 2.3.3 2015-11-11 14:04:19 -05:00
Francis Lachapelle 6aef713d28 Update prototype.js to v1.7.3 2015-11-11 14:02:25 -05:00
Francis Lachapelle 71db3945cb Update CKEditor to version 4.5.4 2015-11-11 14:01:30 -05:00
Francis Lachapelle da230410be Handle ExpandedFolders settings like in v3
This implies a transition where ExpandedFolders can be either a string
or an array.
2015-11-11 13:31:58 -05:00
Francis Lachapelle 82114f75aa Fix Macedonian et Portuguese translations 2015-11-11 12:58:26 -05:00
Francis Lachapelle f8d31a6413 Update NEWS file 2015-11-11 12:04:19 -05:00
Francis Lachapelle 707aabde15 Update Slovenian translation 2015-11-11 12:04:04 -05:00
Francis Lachapelle ceec497aec Fix Portuguese translation 2015-11-11 11:50:32 -05:00
Francis Lachapelle 46f93d8629 Fix Macedonian translation 2015-11-11 11:50:06 -05:00
Francis Lachapelle f2347e13d7 Fix localizable strings in contact editor 2015-11-11 11:49:36 -05:00
Francis Lachapelle 106f8ec314 Improve Portuguese translation 2015-11-11 09:46:21 -05:00
Francis Lachapelle f58554a700 Add Macedonian translation 2015-11-11 09:46:01 -05:00
Francis Lachapelle 76b8de1805 Add Portuguese translation 2015-11-10 16:33:11 -05:00
Ludovic Marcotte 64456c10e5 (fix) additional fix for #3356 2015-11-09 14:30:58 -05:00
Ludovic Marcotte ef7de7c10d (fix) fixed addressbrook-only source entires having a c_uid set 2015-11-06 13:33:36 -05:00
Francis Lachapelle 106c6a24ae (js) Fix dialogs when partially visible
Fixes #2646, #3378
2015-11-06 09:42:09 -05:00
Ludovic Marcotte 863f182e84 (fix) fixed wrong comparison of meta vs. META tag in HTML mails 2015-11-06 09:25:49 -05:00
Francis Lachapelle ba37353e42 (fix) Position of popup menu
Fixes #3381
2015-11-06 09:15:23 -05:00
Francis Lachapelle 1915748e33 Bump version to 2.3.3 2015-11-06 09:15:23 -05:00
Ludovic Marcotte b170ae866f (fix) small fixes 2015-11-06 09:01:33 -05:00
Francis Lachapelle 7c69e37449 (fix) JavaScript syntax error in attendees editor 2015-11-06 08:45:42 -05:00
Ludovic Marcotte 7f26d47cd1 We drop Lucid. 2015-11-05 15:39:08 -05:00
Ludovic Marcotte b55ec52bfe (fix) handle emails having an attachment as their content 2015-11-05 15:21:13 -05:00
Ludovic Marcotte f919df050a (fix) one more fix for #3118 2015-11-05 14:58:58 -05:00
Ludovic Marcotte c2e4f7441d (fix) double declaration of same method 2015-11-05 13:57:51 -05:00
Ludovic Marcotte ca27464984 (fix) added type cast 2015-11-05 13:43:48 -05:00
extrafu 6cbc5573a1 Merge pull request #121 from ethoms/clang-warnings-stage1v2
Stage1b (v2 branch) of clang compiler warning patches.
2015-11-05 13:41:50 -05:00
Euan Thoms e4fae417b9 Stage 8 of clang compiler warning patches.
Update WORequest+SOGo.m
2015-11-05 13:30:40 -05:00
Euan Thoms eda84c7b5d Stage1b (v2 branch) of clang compiler warning patches. 2015-11-06 01:29:26 +08:00
Ludovic Marcotte 9d31023724 (fix) additional fix for #3118 2015-11-05 11:11:18 -05:00
Ludovic Marcotte 41e80bb105 Updated NEWS file regarding PR#120 2015-11-05 09:48:17 -05:00
extrafu e428e74e6c Merge pull request #120 from zentyal/master
Fixes for OpenChange integration from Zentyal
2015-11-05 09:45:51 -05:00
Ludovic Marcotte 7a5e550510 (fix) manually added patch from PR#113 to avoid many "broken" commits 2015-11-05 09:36:11 -05:00
Euan Thoms 0605fdf02a Delete .orig files left over from applying patches to Stage 2. 2015-11-04 13:50:16 -05:00
Euan Thoms d3290dbb8d Stage 2 of clang compiler warning patches. 2015-11-04 13:50:05 -05:00
Ludovic Marcotte 34f626b84c (fix) small fixes around web calendars + sync 2015-11-04 09:21:42 -05:00
Euan Thoms e173c6d621 Stage 9 of clang compiler warning patches. 2015-11-04 08:42:03 -05:00
Euan Thoms 1425bb1c75 Stage 5 of clang compiler warning patches. 2015-11-04 08:41:49 -05:00
Euan Thoms 5b65eaa572 Stage 4 of clang compiler warning patches. 2015-11-04 08:41:36 -05:00
Euan Thoms 892bd693d7 Stage 3 of clang compiler warning patches.
Conflicts:

	UI/PreferencesUI/UIxJSONPreferences.m
2015-11-04 08:41:19 -05:00
Francis Lachapelle 6812cb08dc Display missing events when printing working hours 2015-11-03 09:00:19 -05:00
Francis Lachapelle b62167dd00 Fix Brazilian (Portuguese) translation
Fixes #3355
2015-11-03 08:27:50 -05:00
Jesús García Sáez c8d05d41a5 Merge pull request #201 from zentyal/ejhernandez/sync-expunged-msgs-first-sync
oc-mail: Sync expunged messages on first cache sync
2015-10-26 19:37:01 +01:00
Ludovic Marcotte 1013c473cd (feat) support for ghosted elements on contacts over EAS 2015-10-26 10:25:11 -04:00
Ludovic Marcotte b8d77259cd (feat) we now save the supported elements in EAS collections 2015-10-26 10:15:35 -04:00
Ludovic Marcotte 40cb81fd0b (fix) prevent postal address showing on single line over EAS (#2614) 2015-10-26 10:08:00 -04:00
Jesús García Sáez 2937ba7f30 Merge pull request #200 from zentyal/ejhernandez/always-increase-cn-set-message-read-flag
oc-mail: Always increase the CN when setting the message read flag
2015-10-26 14:38:29 +01:00
Jesús García Sáez 3c6fb10d9f Merge pull request #199 from zentyal/jvalles/missing-calendar-metadata
oc-calendar: force cache synchronisation for a message before aborting
2015-10-26 14:30:38 +01:00
Ludovic Marcotte b378d258cb (fix) add unit test from PR #105 2015-10-23 15:54:33 -04:00
Ludovic Marcotte 3ba1e33eb6 (fix) perms fixes on scripts 2015-10-23 15:39:49 -04:00
Ludovic Marcotte 40a1d5fdbf (fix) inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271) 2015-10-23 14:34:19 -04:00
Ludovic Marcotte 1b44218a35 (fix) avoid crash when replying to a mail with no recipients (#3359) 2015-10-23 12:44:43 -04:00
Ludovic Marcotte df211af604 (fix) fixed br_pt translation + updated transifex (#3355) 2015-10-23 11:32:04 -04:00
Ludovic Marcotte 2c723070c6 (fix) we now return all cards when we receive an empty addressbook-query REPORT 2015-10-21 10:54:15 -04:00
Enrique J. Hernández Blasco 6f5bb3882c oc-mail: Sync expunged messages on first cache sync
There is a use case where this has caused crashes:

A message was hard-deleted using an IMAP client, this is the first
message you deleted in that folder and you have cleared offline
items in the client so a full sync is asked by upper layer.

In that situation, the SyncLastDeleteChangeNumber version property
is not set and return 0 in [getDeletedKeysFromChangeNumber:andCN:inTableType]
making OpenChange to crash while it is asking for deleted fmids
since a given change number.

This is a regression from 18d7070c4a.
2015-10-21 11:24:14 +02:00
Ludovic Marcotte fc9b175f25 (feat) now possible to choose which folders to sync over EAS 2015-10-20 15:33:18 -04:00
Ludovic Marcotte 1716e46210 (fix) last fixes for cache coherency 2015-10-20 08:48:39 -04:00
Juan Vallés 8800cdf641 oc-calendar: force cache synchronisation for a message before aborting
Sometimes we're trying to get the `objectVersion` of a calendar message,
but this message's entry is not in the cache. The method
`synchroniseCache` won't work in this case, so we try to force the
synchronisation of that particular message in order to get the change
number before aborting.
2015-10-20 11:33:10 +02:00
Enrique J. Hernández Blasco d998786ea6 oc-mail: Always increase the CN when setting the message read flag
And perform the real IMAP operation on save method as described by
[MS-OXCFXICS] and [MS-OXCMSG] Section 2.2.3.3, these operations must be
committed when SaveChangesMessage is called.

As it is expected by Outlook to increase the change number when
performing the `SetMessageReadFlag` ROP, if it is not done, the client
tries indefinitely to store that.
2015-10-20 09:56:11 +02:00
Jesús García Sáez a50758cc06 Merge pull request #197 from zentyal/jvalles/revert-content-as-blob-master
Revert "Store c_content in sogo_cache_folder as a BLOB"
2015-10-16 12:43:14 +02:00
Juan Vallés 498cec767b Revert "Store c_content in sogo_cache_folder as a BLOB"
This reverts commit b329513845. It was
causing issues and we'll need to do more testing on this optimisation.
2015-10-16 12:08:50 +02:00
Ludovic Marcotte 8cb580633d (fix) oh well, old crappy distros 2015-10-15 15:54:25 -04:00
Ludovic Marcotte 2b1e060881 (feat) initial S/MIME support for EAS (#3327) 2015-10-15 15:31:46 -04:00
Ludovic Marcotte 3702e79a00 (fix) don't escape quoted strings during versit generation 2015-10-15 12:51:30 -04:00
Ludovic Marcotte beb484da10 (fix) make sure English is always used when generating Date headers using EAS (#3356) 2015-10-14 09:57:56 -04:00
Ludovic Marcotte a54cc22da8 (fix) we no longer always rewrite emails for Outlook when using EAS 2015-10-14 09:29:52 -04:00
Ludovic Marcotte 7f92aca0bd (fix) handle references correctly over EAS (#3365) 2015-10-14 09:27:34 -04:00
Ludovic Marcotte 8d85ca13df (fix) numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849) 2015-10-14 09:21:32 -04:00
Carlos Pérez-Aradros Herce fb7e044bd3 Merge pull request #194 from zentyal/ejhernandez/skip-recurrence-id-without-dtstart
Ignore recurrence-id vevents without dtstart and outside date range
2015-10-07 17:49:56 +02:00
Enrique J. Hernández c08abdb32f Merge pull request #195 from zentyal/jbahillo/view-all-spanish
Changed Spanish string for view all (events/tasks)
2015-10-02 16:25:30 +02:00
Xavy Bahillo c55eab6404 Changed Spanish string for view all (events/tasks)
As current string was referring only to events
2015-10-01 18:16:59 +02:00
Enrique J. Hernández 7680c4091e Merge pull request #191 from zentyal/jvalles/cache-blob
Store c_content in sogo_cache_folder as a BLOB
2015-09-30 16:44:18 +02:00
Enrique J. Hernández 5911563b0f Merge pull request #178 from inverse-inc/v2
Merge from upstream 2.3.2
2015-09-30 14:26:38 +02:00
Enrique J. Hernández Blasco eebf878e89 Ignore recurrence-id vevents without dtstart and outside date range
Happened in an imported vevent from Mozilla Thunderbird.

The crash was:

  Sep 14 15:49:38 sogod [21063]: <0x6442DBF8[SOGoAppointmentFolder]:personal> missing 'c_startdate' in record?
  Sep 14 15:49:38 sogod [21063]: <0x6442DBF8[SOGoAppointmentFolder]:personal> missing 'c_enddate' in record?
  2015-09-14 15:49:38.927 sogod[21063] NGCalendarDateRange.m:37  Assertion failed in NGCalendarDateRange(instance), method initWithStartDate:endDate:.  startDate MUST NOT be nil!
  EXCEPTION: <NSException: 0x7fb96b3c0ed8> NAME:NSInternalInconsistencyException REASON:NGCalendarDateRange.m:37  Assertion failed in NGCalendarDateRange(instance), method initWithStartDate:endDate:.  startDate MUST NOT be nil! INFO:(null)

The relevant ICS file lines are the following ones:

  BEGIN:VEVENT
  UID:040000008200E00074C5B7101A82E00800000000901646A7234BCE01000000000000000010000000E9152C8FF1C27D488C91967FAAFCC2B0
  RECURRENCE-ID:20140513T100000Z
  DURATION:PT1H
  CLASS:PUBLIC
  ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN=krsny >>
      Ann Thierry K:mailto:krsny@example.com
  END:VEVENT
2015-09-30 11:35:12 +02:00
Enrique J. Hernández a8e02b94d4 Merge pull request #193 from zentyal/jag/attach-has-content-id
oc-mail: Fixed false positive in [MAPIStoreAttachment hasContentId]
2015-09-29 19:24:41 +02:00
Jesús García Sáez 6ada034c33 Merge pull request #190 from zentyal/jgarcia/freebusy-multidomain
Fix freebusy for multidomain environments
2015-09-29 13:57:22 +02:00
Javier Amor García c01b21e402 oc-mail: Fixed false positive in [MAPIStoreAttachment hasContentId]
This method does not longer returns true if the content id
was a empty string.
In some case the old false positive triggered the removal of
attachments when sending messages.
2015-09-28 13:06:30 +02:00
Ludovic Marcotte c8c7aaa459 (feat) GUI code to set or now sync flag on address book.
Also thrashed entirely the sync tag concept. Funambol is dead, thank god for that.
2015-09-25 15:21:06 -04:00
Ludovic Marcotte 5409cb2927 (feat) moved FolderSynchronize code to GCS 2015-09-25 15:18:22 -04:00
Juan Vallés b329513845 Store c_content in sogo_cache_folder as a BLOB
This reduces considerably the query time for that column, which can be
rather large.
2015-09-25 12:14:32 +02:00
Jesús García Sáez 0ca6e7c6d3 Fix freebusy for multidomain environments
When returning contacts we have to supply also the domain field.
Because in a multidomain environment UIDField is unique only in
the domain so an user must be identified as uid@domain.

So when creating http requests from client side, we have to use
uid@domain instead of only uid so the SOGoUser created on server
side when parsing the requests is created properly.
2015-09-24 19:00:48 +02:00
Jesús García Sáez bea2415fad Merge pull request #184 from zentyal/jvalles/double-digit-folders
Make folderKey encoding consistent
2015-09-23 18:30:18 +02:00
Jesús García Sáez 180269e39e Merge pull request #185 from zentyal/ejhernandez/return-fai-props-on-available
Avoid crashing Outlook after setting a custom view in a calendar folder
2015-09-22 16:04:42 +02:00
Francis Lachapelle 8a3adeadd2 Rename Slovenian template
Closes #104
2015-09-21 20:37:50 -04:00
Enrique J. Hernández fc8eedf494 Merge pull request #186 from zentyal/ejhernandez/cherry-pick-42f10e1
(fix) safe-guard against not-yet-created calendar of local users
2015-09-18 21:57:25 +02:00
Ludovic Marcotte c8acba8f5a (fix) safe-guard against not-yet-created calendar of local users 2015-09-18 21:56:43 +02:00
Enrique J. Hernández Blasco deca6e383a oc: Include 0x683d0040 as another unknown FAI message property
And returning right type.

We have sorted the FAI properties by number and set the available
name as a comment to ease the tracking afterwards.
2015-09-18 21:45:00 +02:00
Enrique J. Hernández Blasco f3ac7b8334 oc: Return FAI available properties on instance method as well
The operation RopFastTransferSourceCopyTo calls the available
properties for a message using the instance method. It seems the
preferred method by Outlook to synchronise a FAI message. OpenChange
calls the message to get the available properties, so the instance
method is called. As it is specialised to return the custom hack
FAI properties, we have to call that class method instead of using
generic one available at NSObject (MAPIStoreProperties) class.

This avoids crashing the Outlook client after we synchronise the
calendar folder after changing the timeframe view (eg from day view
to month view).
2015-09-18 21:40:01 +02:00
Francis Lachapelle ad8941ba56 Fix URL escaping unti test 2015-09-18 13:48:36 -04:00
Ludovic Marcotte 2bccfcb725 (fix) additional fix for issue #3152 2015-09-17 16:28:42 -04:00
Ludovic Marcotte 4d174b4c6c (fix) typo in previous commit 2015-09-17 16:02:21 -04:00
Ludovic Marcotte 3b15a79c0a (fix) memory leak when sync'ing GCS collections 2015-09-17 15:58:38 -04:00
Ludovic Marcotte b4c2857bb5 (fix) typo in version number for doc 2015-09-16 15:18:10 -04:00
Ludovic Marcotte 6f2eae5d67 Bump version to 2.3.2 2015-09-16 14:37:56 -04:00
Ludovic Marcotte 9a7ea26b80 Update ChangeLog 2015-09-16 13:45:28 -04:00
Ludovic Marcotte 38c9a229c0 Update for the release 2015-09-16 13:44:59 -04:00
Francis Lachapelle e75ec09445 Update NEWS file 2015-09-16 13:31:48 -04:00
Francis Lachapelle 58d03eedc3 Update CKEditor to version 4.5.3 2015-09-16 13:29:37 -04:00
Francis Lachapelle 98ad8f1c0b Update NEWS file 2015-09-16 11:52:46 -04:00
Francis Lachapelle 37b817cd68 Fix options availability in Contacts module cont'd 2015-09-16 11:43:22 -04:00
Francis Lachapelle 9fd8f3edef Add verbose output to "sogo-tool remove" command 2015-09-16 11:39:05 -04:00
Ludovic Marcotte ce11aca2bd (fix) update the doc regarding the temporarily dropping rhel/old debian/ubuntu support 2015-09-16 09:44:29 -04:00
Francis Lachapelle 861057f22f Fix options availability in Contacts module
A user can edit/delete a card from a system addressbook if included in
the "modifiers". Enable the delete/move options of the contextual menu
in this case.
Fixes #3342
2015-09-16 09:38:52 -04:00
Francis Lachapelle b910501afa Fix localization of calendar categories
Fixes #3295
2015-09-15 15:59:31 -04:00
Juan Vallés ba68bd8935 Make folderKey encoding consistent
The folder names are encoded through the `asCSSIdentifier` and
`stringByEncodingImap4FolderName` functions when we store them as folder
keys. In addition, the prefix "folder" is added to the key.

The order in which these operations were done when storing the folder
keys (and reverted when retrieving them) wasn't consistent trough the
code. This led to problems such as creating twice a folder with a digit
at the beginning of its name.

The folder name goes now through the following operations when being
stored as a key (the retrieval reverts these in the reverse order):

 * `stringByEncodingImap4FolderName`
 * `asCSSIdentifier`
 * Add "folder" prefix
2015-09-15 09:57:30 +02:00
extrafu 063f878199 Merge pull request #103 from zentyal/master
Fixes for OpenChange integration
2015-09-14 12:55:03 -04:00
Ludovic Marcotte 6bccd2eea0 (fix) improve spec file regarding disabling OC build 2015-09-14 11:06:54 -04:00
Ludovic Marcotte 652ae763d4 (fix) Start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344) 2015-09-14 10:55:50 -04:00
Juan Vallés 734aba5ddb oc: Fix asCSSIdentifier in openchange_user_cleanup
The method lacked the check for the initial character, which adds an
underscore at the beginning of the strings that start with a digit.
2015-09-14 15:53:07 +02:00
Jesús García Sáez 3da6c90bbe Merge pull request #182 from zentyal/ejhernandez/save-tasks
oc-task: Save tasks from Outlook
2015-09-14 12:24:01 +02:00
Enrique J. Hernández 21a6480834 Merge pull request #181 from zentyal/jvalles/underscore-folders
Improve first character check in CSS identifiers
2015-09-14 11:38:26 +02:00
Ludovic Marcotte 42f10e1929 (fix) safe-guard against not-yet-created calendar of local users 2015-09-10 20:07:42 -04:00
Ludovic Marcotte 03b706e1d8 (fix) disable oc builds on el6 for now 2015-09-10 19:44:45 -04:00
Ludovic Marcotte 2be556299e (fix) slight modification for fix #3293 2015-09-10 19:10:50 -04:00
Enrique J. Hernández Blasco b075a306c6 oc-task: Save tasks from Outlook
It was not working because we try to save as component the
full calendar and its parent was nil. We have to save the calendar
itself to save the task in the personal calendar.
2015-09-10 17:26:41 +02:00
Juan Vallés 36d8fa12d3 Improve first character check in CSS identifiers
A CSS identifier can't start with a digit, so when a folder name does,
a '_' character is appended at the beginning of its CSS identifier.

The check for this first character used the `isdigit()` function, which
takes a `char` argument, while `[self objectAtIndex: 0]` returns a
`unichar`, i.e. a 16-bit unsigned integer. This caused some non-digit
characters to pass this check (e.g. Chinese characters), ending up with
an underscore at the beginning of the folder name.
2015-09-10 10:24:50 +02:00
Ludovic Marcotte 07092828fe (fix) improved EAS speed during initial syncing of large mailboxes (#3293) 2015-09-09 10:20:31 -04:00
Ludovic Marcotte 7c0ba7b72b (fix) avoid too-many LIST commands over EAS (#3294) 2015-09-09 10:12:32 -04:00
Ludovic Marcotte 750a629df8 Updated NEWS file regarding PR#102 2015-09-09 08:24:59 -04:00
extrafu 33a1d2c397 Merge pull request #102 from zentyal/master
Changes for sogo-openchange from Zentyal
2015-09-09 08:19:14 -04:00
Enrique J. Hernández 2e75972df6 Merge pull request #177 from zentyal/jag/remove-compile-warnings
Fix some compilation warnings
2015-09-09 10:15:40 +02:00
Javier Amor García 1fdb44b0a7 Fix some compilation warnings
The warnings were introduced in the fix for attachment with used
the filename extended parameter.

Also removed trailing whitespaces.
2015-09-08 15:39:59 +02:00
Jesús García Sáez 3868ce0fb1 Merge pull request #169 from zentyal/ejhernandez/increase-cn-setting-seen-flag
oc-mail: Increase Change Number after modifying seen flag
2015-09-07 18:46:18 +02:00
Jesús García Sáez fc82eb79f6 Merge pull request #175 from zentyal/ejhernandez/versions-msg-reload-if-needed
oc: load versionsMessage on setting up
2015-09-07 16:48:55 +02:00
Jesús García Sáez 9425868365 Merge pull request #176 from zentyal/ejhernandez/set-read-flag-db
Implement setReadFlag for MAPIStoreDBMessage
2015-09-07 16:35:30 +02:00
Enrique J. Hernández Blasco 6280e4ded6 oc: Implement setReadFlag for MAPIStoreDBMessage
This is an utility for testing as I don't see any added value
for real scenario but according to [MS-OXCMSG] all messages
can have PidTagMessageFlags.
2015-09-04 16:16:00 +02:00
Enrique J. Hernández Blasco f19074334c oc: Use enum mapistore_error as returned value for setReadFlag 2015-09-04 16:15:05 +02:00
Enrique J. Hernández Blasco b702e40ebb oc: load versionsMessage on setting up
versionsMessage object could have outdated version in a root folder
in the following case:

* Download latest contents using FXBuffer
  * versionsMessage is updated by synchroniseCache
* OpenMessage from last FXBuffer
  * Setup versions message as root folder
* Get Predecessor Change List from that message

We could just reload if needed the versions message if something
is missing but I don't know if that situation fixes more than this
one.
2015-09-03 23:01:21 +02:00
Enrique J. Hernández 190f27ed7c Merge pull request #174 from zentyal/jag/test-quote-mime-header-value
Test for [NGMimeMessageGenerator generateDataForHeaderField:value:]
2015-09-01 16:15:23 +02:00
Javier Amor García cbc3e3e97d Test for [NGMimeMessageGenerator generateDataForHeaderField:value:]
The NGMimeMessageGenerator can be found in sope
2015-09-01 09:09:54 +02:00
Javier Amor García 361d5ea3a6 Added method [SOGoTest stringFromDiffBetween:and:] 2015-09-01 09:09:54 +02:00
Jesús García Sáez 24e3071e24 Merge pull request #170 from zentyal/jag/attachments-nolatin-filename
sogo-openchange: Support attachments with filename extended parameter
2015-08-26 15:15:15 +02:00
Jesús García Sáez 1f29198495 Merge pull request #172 from zentyal/ejhernandez/save-draft-right-change-key
oc-mail: Return right change key after saving a draft mail
2015-08-26 13:37:42 +02:00
Jesús García Sáez 916442f9cf Merge pull request #171 from zentyal/ejhernandez/restriction-cn-ge
oc-mail: Implement ChangeNumber >= restriction
2015-08-25 13:17:20 +02:00
Enrique J. Hernández Blasco 47859b76d6 oc-mail: Return right change key after saving a draft mail
After saving a draft mail (this is done automatically by Outlook)
a GetProps call is done checking the PidTagChangeKey has been
updated properly. Without this patch, it returned MAPI_E_NOT_FOUND.

With this patch, we addressed that problem and we have updated
the Predecessor Change List metadata for the draft mail with the
change key provided by the client to avoid conflicting messages
whenever it is possible.
2015-08-24 23:42:38 +02:00
Enrique J. Hernández Blasco 3a60b6e38e oc-mail: Implement ChangeNumber >= restriction
And warn when other operator than > or >= is used.

This allows OpenChange to query for change numbers starting from
a given point.
2015-08-24 15:39:05 +02:00
Javier Amor García ae3ac0a09a oc-mail: Support attachments with filename extended parameter
The attachments which used a extended parameter for their filename
('filename*=') where silently dropped.
This was because MAPIStore was only looking for no-extended filename
parameter.
The solution is using the 'filename' from the
SOGOExtension of the NSDictionary interface.
2015-08-24 14:19:47 +02:00
Ludovic Marcotte 27a19ac956 (fix) Session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less 2015-08-21 10:30:47 -04:00
Enrique J. Hernández Blasco 27b9b7bfa7 oc-mail: Increase Change Number after modifying seen flag
It is required when you are using SynchronizeImportReadStateChanges ROP
to update the MetaTagCnsetRead meta property.

See [MS-OXCFXICS] Section 3.2.5.9.4.6

This could lead to sync issues.
2015-08-10 18:38:39 +02:00
Julio J. García Martín 18f758cb0a Merge pull request #159 from Zentyal/ejhernandez/fixes-on-move
Maintain PidTagPredecessorChangeList with client's data
2015-08-04 18:13:19 +02:00
Enrique J. Hernández Blasco 321672e2c3 oc: Update predecessor change list on saving
There were cases where only the change key was updated (GCS) or
others were the change key was updated with wrong info.

This changeset has as goal to update the predecessor change list
and, change key if required, on saving taking into account the latest information
given by the client in high level ROPs such as ImportMessageMove
or SetProperties, and merge it with information provided by the server
backend (IMAP server, SOGo DB) using `synchroniseCache`.

For more details about `PidTagChangeKey` and `PidTagPredecessorChangeList`
property values check [MS-OXCFXICS] Section 2.2.1.2
2015-08-04 08:41:38 +02:00
Enrique J. Hernández Blasco 8d9b54815c oc: Receive new predecessor change list parameter on move copy op
This is to apply new API introduced by 85e2d7c commit in OpenChange
repository.
2015-08-04 08:41:38 +02:00
Enrique J. Hernández Blasco 1dbfc18379 oc: Update to new XID structure definition
From ede986f commit from OpenChange repository.
2015-08-04 08:41:38 +02:00
Ludovic Marcotte 05b330e11b (fix) added changes from PR94 to avoid big merge conflicts 2015-08-03 11:06:30 -04:00
Luc Charland 307876e6c7 cleaned up a few comments 2015-08-03 11:01:33 -04:00
Luc Charland ac6b299988 removed double Name translation in 5 languages 2015-08-03 10:13:23 -04:00
Ludovic Marcotte 234db671ad Removed merged in templates from v3 2015-08-03 10:12:40 -04:00
Luc Charland 2d33b01f31 Removing colons
Conflicts:

	UI/Templates/ContactsUI/UIxContactEditorTemplate.wox
	UI/Templates/ContactsUI/UIxContactFolderLinksTemplate.wox
	UI/Templates/MailerUI/UIxMailMainFrame.wox
	UI/Templates/MainUI/SOGoRootPage.wox
	UI/Templates/PreferencesUI/UIxAccountEditor.wox
	UI/Templates/PreferencesUI/UIxPreferences.wox
	UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox
	UI/Templates/SchedulerUI/UIxAppointmentViewTemplate.wox
	UI/Templates/SchedulerUI/UIxCalMainView.wox
	UI/Templates/SchedulerUI/UIxCalendarFolderLinksTemplate.wox
	UI/Templates/SchedulerUI/UIxTaskEditorTemplate.wox
	UI/Templates/SchedulerUI/UIxTaskViewTemplate.wox
	UI/WebServerResources/angular-material
2015-08-03 10:12:02 -04:00
Luc Charland 490354427d fixed Appointments path 2015-08-03 09:56:14 -04:00
Luc Charland 22ce38808a cleaned up a few comments 2015-08-03 09:55:52 -04:00
Luc Charland 82aca2cfe5 added first draft of verify_translation 2015-08-03 09:55:17 -04:00
Ludovic Marcotte b17488ff25 Updated NEWS file regarding PR93 2015-08-03 09:18:20 -04:00
extrafu ff3557598f Merge pull request #93 from Zentyal/jjgarcia/contribute-back
Changes for sogo-openchange from Zentyal
2015-08-03 09:13:55 -04:00
Julio García 26bd1b30fa Revert "Merge pull request #164 from Zentyal/jgarcia/revert-merge-inverse"
This reverts commit 1404dbdb88, reversing
changes made to 72cbd9a45f.
2015-08-03 09:48:06 +02:00
Enrique J. Hernández 0ce04fd4b6 Merge pull request #165 from Zentyal/jgarcia/uids_login_party_hard
SOGoUser bad initialization on multidomain environment
2015-07-31 18:46:15 +02:00
Jesús García Sáez f3656f7571 Merge pull request #166 from Zentyal/ejhernandez/update-cn-setting-folder-props
oc: Update ChangeNumber after setting folder properties
2015-07-30 14:49:56 +02:00
Enrique J. Hernández Blasco 2aba083147 oc: Update ChangeNumber after setting folder properties
As required by operations like SynchronizationImportHierarchyChanges
a new change number must be generated when a change in a folder
is set. This affects to subfolders.

See [MS-OXCFXICS] Section 3.2.5.9.4.3 for details.
2015-07-30 10:07:55 +02:00
Jesús García Sáez 9414df26c5 getUIDForEmail works on multidomain: returns login
This method is used everywhere to try to retrieve the login of the user
(and normally use the return value to [SOGoUser initwithLogin: ...])

In multidomain environments (with DomainLessLogin = false) there were
several paths (mostly in SOGoAppointmentObject.m) that were trying to
create SOGoUser objects with incorrect login: using only the uid part,
not full email. Then like domain based uid was enabled, these users
had DomainLessLogin set to true and further calls tried to authenticate
only with the uid part (and they should not).

This affects to several methods in:

  * ActiveSync/SOGoActiveSyncDispatcher.m
  * Appointments/SOGoAppointmentFolder.m
  * Appointments/SOGoAppointmentObject.m
  * Appointments/SOGoCalendarComponent.m
  * SOGoSAML2Session.m

Probably a few features related with calendars are now fixed or working
as intended in multidomain environments where the email is used as login
2015-07-29 17:40:56 +02:00
Jesús García Sáez 7496b92da5 Merge pull request #158 from Zentyal/jag/mapistorefolder-getpidtagdisplayname
oc-mail: Added [MAPIStoreFolder getPidTagDisplayName:inMemCtx:]
2015-07-28 18:08:20 +02:00
Jesús García Sáez e38b0ac64f Merge pull request #163 from Zentyal/ejhernandez/subject-fallback
oc-mail: Fallback to PidTagSubject unicode when creating new mail
2015-07-28 17:16:11 +02:00
Julio J. García Martín 1404dbdb88 Merge pull request #164 from Zentyal/jgarcia/revert-merge-inverse
Revert merge from inverse
2015-07-28 12:57:31 +02:00
Jesús García Sáez 3dc8cc78ea Revert merge from inverse
https://github.com/Zentyal/sogo/pull/150

Because the login on web with the use of outlook is broken after
including the DomainLessLogin feature
2015-07-28 12:37:33 +02:00
Jesús García Sáez 72cbd9a45f Merge pull request #162 from Zentyal/ejhernandez/fix-compilation-warn
oc: Fix compilation warning in MAPIStoreContext
2015-07-27 13:41:36 +02:00
Enrique J. Hernández Blasco 10ac445f7c oc-mail: Fallback to PidTagSubject unicode when creating new mail
Some clients such as OpenChange client does not send the following
properties PidTagNormalizedSubject or PidTagSubjectPrefix as
suggested by [MS-OXCMAIL].
2015-07-27 11:18:14 +02:00
Enrique J. Hernández Blasco c5b5a4243d oc: Fix compilation warning in MAPIStoreContext 2015-07-25 09:56:21 +02:00
Ludovic Marcotte 304ffa2099 (fix) SOGoMaximumMessageSubmissionCount should be independant from SOGoMaximumRecipientCount 2015-07-24 11:19:57 -04:00
Francis Lachapelle e4ad255fc2 (fix) chmod 644 UI/MainUI/Slovenian.lproj/Locale 2015-07-23 14:09:27 -04:00
Francis Lachapelle 390a853471 (fix) Display of whitelisted attendees in Firefox
Fixes #3285
2015-07-23 14:07:34 -04:00
Ludovic Marcotte dd9b5e18b6 Updates for the release 2015-07-23 11:51:36 -04:00
Francis Lachapelle 5c017c19fc Update Spanish (Argentina) translation 2015-07-23 11:49:07 -04:00
Francis Lachapelle dec152e8e5 Update CKEditor to version 4.5.1 2015-07-23 11:48:51 -04:00
Francis Lachapelle 4922e6004d Add missing templates for Basque language 2015-07-23 11:14:11 -04:00
Francis Lachapelle b91972d1dc Add missing template for Slovenian language 2015-07-23 11:14:09 -04:00
Francis Lachapelle 5c3cdda186 Add Chinese (Taiwan) translation 2015-07-23 11:14:08 -04:00
Ludovic Marcotte 4b19e64071 (fix) fallback to utf-8/latin1 in rare decoding error cases - this is ugly 2015-07-23 10:22:36 -04:00
Francis Lachapelle b6dee056ca (fix) Return value of [iCalPerson+SOGo uidInDomain:] 2015-07-23 08:46:08 -04:00
Enrique J. Hernández 372247a643 Merge pull request #160 from Zentyal/jgarcia/useless-indexing
oc: remove useless mapistore_indexing call
2015-07-23 08:58:36 +02:00
Francis Lachapelle 6830d6d930 Calendar module optimizations in multidomain setup 2015-07-22 17:23:02 -04:00
Francis Lachapelle 366ad44cd1 Fix .wod files for English and Basque 2015-07-22 17:22:26 -04:00
Francis Lachapelle 9430f40101 Add Slovenian translation 2015-07-22 17:22:26 -04:00
Ludovic Marcotte b7c2d8ae95 (fix) we now respect the cr/lf in event's description (#3228) 2015-07-22 14:49:09 -04:00
Ludovic Marcotte f03e7a6604 (doc) ModuleConstraints for EAS 2015-07-22 11:54:50 -04:00
Ludovic Marcotte 4e7a3ce5fa (fix) fixed EAS conversation ID issues on BB10 (#3152) 2015-07-22 10:26:09 -04:00
Ludovic Marcotte 61c542cb95 (fix) make sure ActiveSync is always usable with SQL-based auth sources 2015-07-22 10:12:47 -04:00
Ludovic Marcotte d2640e1501 (fix) fixed computation of GlobalObjectId (#3235) 2015-07-22 09:59:36 -04:00
Ludovic Marcotte 20b91cc7bf (fix) improved EAS email flagging handling (#3140) 2015-07-22 09:46:06 -04:00
Ludovic Marcotte b05f7a5beb (fix) compilation warning 2015-07-22 09:25:29 -04:00
Ludovic Marcotte 3a61b7bae4 Android EAS Lollipop fixes (#3268 and #3269) 2015-07-22 09:15:34 -04:00
Jesús García Sáez 7bde181d3f oc: check ret value get_new_folderID
On [MAPIStoreContext idForObjectWithKey: key inFolderUrl: url] check the ret value
of mapistore_indexing_get_new_folderID. This should never happen (oh my...) but
if this happens it will be reported
2015-07-21 19:47:17 +02:00
Jesús García Sáez 1336a1d5a8 oc: remove useless mapistore_indexing call
[mapping registerURL ...] will insert the mappingId in indexing database
there is no need to call, again, mapistore_indexing_record_add_mid
2015-07-21 19:47:17 +02:00
Enrique J. Hernández cf606e3224 Merge pull request #157 from Zentyal/jgarcia/wocontext_broken_like_hell
Fix sharing imap connections between different clients
2015-07-21 18:38:09 +02:00
Jesús García Sáez c9f2fe2f85 oc-folder: user context activation on rootFolders
Several fixes related with userContext activation
2015-07-21 11:08:03 +02:00
Jesús García Sáez 10bc15d41d oc-folder: activate user context when needed
Before creating sogo objects
2015-07-21 11:08:02 +02:00
Jesús García Sáez 393b6d5128 oc-folder: simplify lookupFolder method
use activate and no need to set wocontext, it's already set properly
2015-07-21 11:08:02 +02:00
Jesús García Sáez 68ae978b13 oc-mail: activate user context needed
Before creating new sogo objects
2015-07-21 11:08:02 +02:00
Jesús García Sáez f4e3b98042 Nothing changed: format and hoarding code removed 2015-07-21 11:08:02 +02:00
Jesús García Sáez cfab18e1b8 oc: activate user context on initialization 2015-07-21 11:08:02 +02:00
Javier Amor García 959b4e015d [SOgoMailFolder displayName] decodes IMAP4 2015-07-20 15:19:43 +02:00
Ludovic Marcotte 7c6c78d13b Updated NEWS file regarding PR from Zentyal folks 2015-07-19 13:37:08 -04:00
extrafu 7db109525d Merge pull request #90 from Zentyal/master
Latest changes for sogo-openchange
2015-07-19 13:36:56 -04:00
Javier Amor García 2ce85f6c5c oc-mail: Added [MAPIStoreFolder getPidTagDisplayName:inMemCtx:]
This getter is necessary to decode folder names
in utf7 encoding.
2015-07-17 18:23:40 +02:00
Jesús García Sáez 7d2f96e96b oc: use activate instead of activateWithUser
100% of the times the user is the one that MAPIStoreUserContext
already has, so no need to have it as an argument.
2015-07-15 11:26:05 +02:00
Jesús García Sáez 3bf7afdfb7 oc: remove and disable userContext after each call
This adds [MAPIStoreUserContext activate] method to use
it instead of activateWithUser.

A cleanup operation is executed after each public function
so there won't be any conflicts with future calls.

In practice, this will deactivate the current user context set on
MAPIApp, this means two things: (1) set nil as current user context
on MAPIApp and (2) remove woContext from current thread dictionary
2015-07-15 11:25:45 +02:00
Jesús García Sáez 503a70118f oc: remove tabs and unused code 2015-07-14 19:19:58 +02:00
Jesús García Sáez 928b6b596e oc: OC_DEBUG inside macros
These macros are used before and after every performed
operation
2015-07-14 19:19:51 +02:00
Julio J. García Martín d0d93a297b Merge pull request #156 from Zentyal/jjgarcia/remove-not-needed-code
Remove not needed block in SoObjects/SOGo/SOGoUserManager.m
2015-07-13 09:13:21 +02:00
Julio García 936c38c43e Remove not needed block in SoObjects/SOGo/SOGoUserManager.m 2015-07-07 14:25:17 +02:00
Jesús García Sáez 7da7bdc1bf Merge pull request #150 from Zentyal/merge-upstream
Merge upstream
2015-07-06 18:57:45 +02:00
Jesús García Sáez 1e101ef01a Merge branch 'master' into merge-upstream
Conflicts:
	SoObjects/SOGo/SOGoUserManager.m
2015-07-06 18:54:35 +02:00
Enrique J. Hernández 3171257ef4 Merge pull request #153 from Zentyal/jag/active-sync-access
ActiveSync: can be controlled by module constraint
2015-07-03 08:42:55 +02:00
Julio J. García Martín 2c8d65843f Merge pull request #154 from Zentyal/ejhernandez/fix-crash-no-object-type-rec
oc: Check PidTagObjectType before dereferencing on ModifyRecipients
2015-07-01 10:57:25 +02:00
Enrique J. Hernández Blasco 62b16f4ef3 oc: Check PidTagObjectType before dereferencing on ModifyRecipients
This is crashing when the PidTagObjectType property is set for
some recipient and not for others.

If the property is missing, then no object type for the recipient
is assumed.
2015-06-29 23:53:12 +02:00
Ludovic Marcotte b86b1afdb9 Fix for dfecce7381 2015-06-29 13:49:41 -04:00
Julio García 8e047aaa9c Merge remote-tracking branch 'upstream/master' into merge-upstream
Conflicts:
	SoObjects/SOGo/SOGoUserManager.m
	Tools/SOGoToolRestore.m
2015-06-26 09:41:58 +02:00
Julio García dc2cc14d25 Revert "Revert "(fix) more fixes and clarifications on the mutli-domain settings""
This reverts commit 3dd8bddc0e.
2015-06-26 09:35:47 +02:00
Julio García 3f1a43cc13 Revert "Revert "(fix) more multi-domain fixes and cleanups""
This reverts commit 90e11d76f6.
2015-06-26 09:35:37 +02:00
Julio García 67d0d95869 Revert "Revert "Fixed mess regarding uid/realUID""
This reverts commit 96d88de564.
2015-06-26 09:35:26 +02:00
Francis Lachapelle d16bf7c1a7 (fix) Tasks list display in Calendar module
Fixes #3267
2015-06-24 07:48:01 -04:00
Francis Lachapelle b527099907 (fix) Events list display in Calendar module
Fixes #3261
2015-06-23 09:25:29 -04:00
Javier Amor García 72aab7f83f ActiveSync: can be controlled by module constraint
Added 'ActiveSync' module constraint. If constraint is not
honored the ActiveSync request returns a forbidden (403).
2015-06-23 09:11:45 +02:00
Ludovic Marcotte dfecce7381 (feat) added support for DeletesAsMoves over EAS 2015-06-22 09:19:54 -04:00
Francis Lachapelle 82798ccb99 Change signature of [iCalAlarm alarmForEvent:..]
The first argument doesn't have to be a iCalRepeatableEntity; a simple
iCalEntity is enough.
2015-06-19 22:11:24 -04:00
Ludovic Marcotte 3809c002ed Revert "(fix) ensure calendar objects have a (fake) filename"
This reverts commit a145fdbb53.
2015-06-17 15:57:16 -04:00
Ludovic Marcotte d9b4abc2f1 (fix) improved memory usage 2015-06-17 08:46:09 -04:00
Jesús García Sáez c1b0fd53dc Merge pull request #100 from Zentyal/jag/recipient-from-client-call
mapistore: can use client data to get recipient address
2015-06-08 14:32:31 +02:00
Julio J. García Martín c0a22ead1d Merge pull request #146 from Zentyal/merge-from-upstream
Merge from upstream
2015-06-04 18:53:24 +02:00
Enrique J. Hernández d8e37cd7d8 Merge pull request #142 from Zentyal/jag/sogo-tool-create-folder
sogo-tool: Added create-folder command
2015-06-02 17:43:03 +02:00
Javier Amor García 7540cc3e33 oc: can use client data to get recipient address
Before this change, the recipient address was only extracted from the sogo
user object. This made mail to groups undeliverable.
Now if we do not have mail addresses from user object,
we try to use parameters from the client call.
2015-06-02 15:07:50 +02:00
Jesús García Sáez d92b0341fb sogo-tool restore works in multidomain environments
c_uid is not unique globally when multidomain is enabled
2015-06-02 13:17:43 +02:00
Javier Amor García 5e84f2817f sogo-tool: Added create-folder command 2015-06-02 13:17:35 +02:00
Julio García c61eca27a1 Merge branch 'upstream-master' into merge-from-upstream
Conflicts:
	SoObjects/SOGo/LDAPSource.m
	SoObjects/SOGo/SOGoUserManager.m
2015-06-02 12:27:20 +02:00
Julio García 96d88de564 Revert "Fixed mess regarding uid/realUID"
This reverts commit 4f303f8aa2.
2015-06-02 12:24:56 +02:00
Julio García 90e11d76f6 Revert "(fix) more multi-domain fixes and cleanups"
This reverts commit ed42d1e26b.
2015-06-02 12:24:44 +02:00
Julio García 3dd8bddc0e Revert "(fix) more fixes and clarifications on the mutli-domain settings"
This reverts commit d07df554f3.
2015-06-02 12:24:32 +02:00
Enrique J. Hernández 95f11f6d0d Merge pull request #145 from Zentyal/jvalles/appointment-updates
oc-calendar: Improve check for appointment updates
2015-06-01 22:48:45 +02:00
Juan Vallés 1ec53a063c oc-calendar: Improve check for appointment updates
The UID was being used to check if  the changes in an appointment had been made by
its organiser. In this case, the UID is the user name, without taking the domain into account.
The `owner` variable, however, is a full email address, so the comparison was never successful. This
caused the update notification mail not to be sent.
2015-06-01 17:09:48 +02:00
Enrique J. Hernández cab5846e37 Merge pull request #143 from Zentyal/jgarcia/crash-session
Safe decoding of secured value
2015-06-01 17:01:07 +02:00
Jesús García Sáez 76f80c507c safe decoding of secured value
This was crashing with invalid value (shorter than it should be or nil value)
2015-05-28 12:35:18 +02:00
Jesús García Sáez afed233131 Merge pull request #141 from Zentyal/ejhernandez/bson-encode-datestz-offset
Use UTC offset to encode NSCalendarDate in BSON
2015-05-27 14:50:14 +02:00
Enrique J. Hernández Blasco b04b66fd79 Use UTC offset to encode NSCalendarDate in BSON
Using name abbreviation in Objective-C is discouraged
as it stated in the following link:

https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSTimeZone_Class/index.html#//apple_ref/occ/clm/NSTimeZone/timeZoneWithAbbreviation:

Indeed, the GNUStep library is generating the name
abbreviation using the system tzdata information
but parsing this data using GNUStep resources making
changes like this:

http://mm.icann.org/pipermail/tz-announce/2014-August/000023.html

Not working until GNUStep reaches these tzdata changes.

Using the UTC offset we are losing the position in planet
Earth but it could be considered valid looking at other
bson implementations.
2015-05-26 21:42:57 +02:00
Enrique J. Hernández 13f2ac9cbb Merge pull request #138 from Zentyal/jgarcia/fix-multidomain-web
Avoid reading emails from other domains (info@foo vs info@bar)
2015-05-25 22:17:42 +02:00
Jesús García Sáez 7dc333e790 Fix warning distinct Objective-C type
failedCount is a NSDictionary *, not NSMutableDictionary *
2015-05-25 17:33:46 +02:00
Jesús García Sáez cc62576fef Avoid cache entries with uid@domain@domain prefix
Depend of the current workflow these paths are reached with
username as uid and sometimes as uid@domain. So in multidomain
environments only append @domain when needed.
2015-05-25 17:33:32 +02:00
Jesús García Sáez a1d07c59af Merge pull request #137 from Zentyal/jag/change-passsword-exceptions
Return appropiate error code when changing AD password with incorrect…
2015-05-23 02:23:02 +02:00
Enrique J. Hernández 0a37652dd6 Merge pull request #139 from Zentyal/jgarcia/get-sources-from-your-domain
Try to authenticate only against sources of the given domain
2015-05-22 20:43:41 +02:00
Javier Amor García 725dea5f6f Show 'complexity password' error instead of a generic error
There is not discrimination between complexity and
length errors because LDAP does not
return different error codes for each case.
2015-05-22 14:59:49 +02:00
Jesús García Sáez 76dd53b461 Fix conflicts with externalLoginWithEmail + DomainBasedUid
On multidomain environment (SOGoEnableDomainBasedUID) with email for imap
authentication (SOGoForceExternalLoginWithEmail) we need to use uid@domain
instead of just uid in method getEmailForUID
2015-05-21 17:13:18 +02:00
Jesús García Sáez 020c895925 Avoid uid+attributes entries on shared cache
In multidomain environments this will produce that info@domain1.com
can read info@domain2.com emails when info@domain2.com log in after
info@domain1.com is already logged in.

If multidomain is not enabled, this action is not needed because
uid+attributes has been already saved on shared cache
2015-05-21 17:13:18 +02:00
Ludovic Marcotte f4963df50f (fix) avoid appending the domain unconditionally when SOGoEnableDomainBasedUID is set to YES 2015-05-21 17:13:11 +02:00
Jesús García Sáez 7160d94e91 Set domain before fetching auth sources
In multidomain environment right now we are trying to authenticate against
all sources defined in sogo.conf because the domain is not set at this point.

In sogo.conf we have to specify the domain a source is useful for, so with this
patch instead of 'n' tries of authentication we will perform only 1 (in a scenario
where we have 1 source per domain, and we have 'n' domains).
2015-05-20 18:33:11 +02:00
619 changed files with 23293 additions and 13685 deletions

No files matched your search

+44
View File
@@ -18,15 +18,19 @@ trans.fr = UI/MailerUI/French.lproj/Localizable.strings
trans.hu = UI/MailerUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MailerUI/Icelandic.lproj/Localizable.strings
trans.it = UI/MailerUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/MailerUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MailerUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MailerUI/Polish.lproj/Localizable.strings
trans.pt = UI/MailerUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MailerUI/Russian.lproj/Localizable.strings
trans.sk = UI/MailerUI/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/MailerUI/Slovenian.lproj/Localizable.strings
trans.sv = UI/MailerUI/Swedish.lproj/Localizable.strings
trans.uk = UI/MailerUI/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/MailerUI/ChineseTaiwan.lproj/Localizable.strings
[sogo.ui-preferencesui]
source_file = UI/PreferencesUI/English.lproj/Localizable.strings
@@ -45,15 +49,19 @@ trans.fr = UI/PreferencesUI/French.lproj/Localizable.strings
trans.hu = UI/PreferencesUI/Hungarian.lproj/Localizable.strings
trans.is = UI/PreferencesUI/Icelandic.lproj/Localizable.strings
trans.it = UI/PreferencesUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/PreferencesUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/PreferencesUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/PreferencesUI/Polish.lproj/Localizable.strings
trans.pt = UI/PreferencesUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/PreferencesUI/Russian.lproj/Localizable.strings
trans.sk = UI/PreferencesUI/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/PreferencesUI/Slovenian.lproj/Localizable.strings
trans.sv = UI/PreferencesUI/Swedish.lproj/Localizable.strings
trans.uk = UI/PreferencesUI/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/PreferencesUI/ChineseTaiwan.lproj/Localizable.strings
[sogo.ui-scheduler]
source_file = UI/Scheduler/English.lproj/Localizable.strings
@@ -72,15 +80,19 @@ trans.fr = UI/Scheduler/French.lproj/Localizable.strings
trans.hu = UI/Scheduler/Hungarian.lproj/Localizable.strings
trans.is = UI/Scheduler/Icelandic.lproj/Localizable.strings
trans.it = UI/Scheduler/Italian.lproj/Localizable.strings
trans.mk_MK = UI/Scheduler/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Scheduler/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Scheduler/Polish.lproj/Localizable.strings
trans.pt = UI/Scheduler/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Scheduler/Russian.lproj/Localizable.strings
trans.sk = UI/Scheduler/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/Scheduler/Slovenian.lproj/Localizable.strings
trans.sv = UI/Scheduler/Swedish.lproj/Localizable.strings
trans.uk = UI/Scheduler/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/Scheduler/ChineseTaiwan.lproj/Localizable.strings
[sogo.ui-contacts]
source_file = UI/Contacts/English.lproj/Localizable.strings
@@ -99,15 +111,19 @@ trans.fr = UI/Contacts/French.lproj/Localizable.strings
trans.hu = UI/Contacts/Hungarian.lproj/Localizable.strings
trans.is = UI/Contacts/Icelandic.lproj/Localizable.strings
trans.it = UI/Contacts/Italian.lproj/Localizable.strings
trans.mk_MK = UI/Contacts/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Contacts/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Contacts/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Contacts/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Contacts/Polish.lproj/Localizable.strings
trans.pt = UI/Contacts/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Contacts/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Contacts/Russian.lproj/Localizable.strings
trans.sk = UI/Contacts/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/Contacts/Slovenian.lproj/Localizable.strings
trans.sv = UI/Contacts/Swedish.lproj/Localizable.strings
trans.uk = UI/Contacts/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/Contacts/ChineseTaiwan.lproj/Localizable.strings
[sogo.ui-mainui]
source_file = UI/MainUI/English.lproj/Localizable.strings
@@ -126,15 +142,19 @@ trans.fr = UI/MainUI/French.lproj/Localizable.strings
trans.hu = UI/MainUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MainUI/Icelandic.lproj/Localizable.strings
trans.it = UI/MainUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/MainUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MainUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MainUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MainUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MainUI/Polish.lproj/Localizable.strings
trans.pt = UI/MainUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MainUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MainUI/Russian.lproj/Localizable.strings
trans.sk = UI/MainUI/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/MainUI/Slovenian.lproj/Localizable.strings
trans.sv = UI/MainUI/Swedish.lproj/Localizable.strings
trans.uk = UI/MainUI/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/MainUI/ChineseTaiwan.lproj/Localizable.strings
[sogo.ui-common]
source_file = UI/Common/English.lproj/Localizable.strings
@@ -153,15 +173,19 @@ trans.fr = UI/Common/French.lproj/Localizable.strings
trans.hu = UI/Common/Hungarian.lproj/Localizable.strings
trans.is = UI/Common/Icelandic.lproj/Localizable.strings
trans.it = UI/Common/Italian.lproj/Localizable.strings
trans.mk_MK = UI/Common/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Common/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Common/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Common/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Common/Polish.lproj/Localizable.strings
trans.pt = UI/Common/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Common/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Common/Russian.lproj/Localizable.strings
trans.sk = UI/Common/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/Common/Slovenian.lproj/Localizable.strings
trans.sv = UI/Common/Swedish.lproj/Localizable.strings
trans.uk = UI/Common/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/Common/ChineseTaiwan.lproj/Localizable.strings
[sogo.ui-administrationui]
source_file = UI/AdministrationUI/English.lproj/Localizable.strings
@@ -180,15 +204,19 @@ trans.fr = UI/AdministrationUI/French.lproj/Localizable.strings
trans.hu = UI/AdministrationUI/Hungarian.lproj/Localizable.strings
trans.is = UI/AdministrationUI/Icelandic.lproj/Localizable.strings
trans.it = UI/AdministrationUI/Italian.lproj/Localizable.strings
trans.mk_MK = UI/AdministrationUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/AdministrationUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/AdministrationUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/AdministrationUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/AdministrationUI/Polish.lproj/Localizable.strings
trans.pt = UI/AdministrationUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/AdministrationUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/AdministrationUI/Russian.lproj/Localizable.strings
trans.sk = UI/AdministrationUI/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/AdministrationUI/Slovenian.lproj/Localizable.strings
trans.sv = UI/AdministrationUI/Swedish.lproj/Localizable.strings
trans.uk = UI/AdministrationUI/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/AdministrationUI/ChineseTaiwan.lproj/Localizable.strings
[sogo.soobjects-appointments]
source_file = SoObjects/Appointments/English.lproj/Localizable.strings
@@ -207,15 +235,19 @@ trans.fr = SoObjects/Appointments/French.lproj/Localizable.strings
trans.hu = SoObjects/Appointments/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Appointments/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Appointments/Italian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Appointments/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Appointments/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Appointments/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Appointments/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Appointments/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Appointments/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Appointments/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Appointments/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Appointments/Slovak.lproj/Localizable.strings
trans.sl_SI = SoObjects/Appointments/Slovenian.lproj/Localizable.strings
trans.sv = SoObjects/Appointments/Swedish.lproj/Localizable.strings
trans.uk = SoObjects/Appointments/Ukrainian.lproj/Localizable.strings
trans.zh_TW = SoObjects/Appointments/ChineseTaiwan.lproj/Localizable.strings
[sogo.soobjects-contacts]
source_file = SoObjects/Contacts/English.lproj/Localizable.strings
@@ -234,15 +266,19 @@ trans.fr = SoObjects/Contacts/French.lproj/Localizable.strings
trans.hu = SoObjects/Contacts/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Contacts/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Contacts/Italian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Contacts/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Contacts/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Contacts/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Contacts/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Contacts/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Contacts/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Contacts/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Contacts/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Contacts/Slovak.lproj/Localizable.strings
trans.sl_SI = SoObjects/Contacts/Slovenian.lproj/Localizable.strings
trans.sv = SoObjects/Contacts/Swedish.lproj/Localizable.strings
trans.uk = SoObjects/Contacts/Ukrainian.lproj/Localizable.strings
trans.zh_TW = SoObjects/Contacts/ChineseTaiwan.lproj/Localizable.strings
[sogo.soobjects-mailer]
source_file = SoObjects/Mailer/English.lproj/Localizable.strings
@@ -261,15 +297,19 @@ trans.fr = SoObjects/Mailer/French.lproj/Localizable.strings
trans.hu = SoObjects/Mailer/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Mailer/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Mailer/Italian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Mailer/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Mailer/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Mailer/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Mailer/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Mailer/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Mailer/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Mailer/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Mailer/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Mailer/Slovak.lproj/Localizable.strings
trans.sl_SI = SoObjects/Mailer/Slovenian.lproj/Localizable.strings
trans.sv = SoObjects/Mailer/Swedish.lproj/Localizable.strings
trans.uk = SoObjects/Mailer/Ukrainian.lproj/Localizable.strings
trans.zh_TW = SoObjects/Mailer/ChineseTaiwan.lproj/Localizable.strings
[sogo.ui-mailpartviewers]
source_file = UI/MailPartViewers/English.lproj/Localizable.strings
@@ -288,12 +328,16 @@ trans.fr = UI/MailPartViewers/French.lproj/Localizable.strings
trans.hu = UI/MailPartViewers/Hungarian.lproj/Localizable.strings
trans.is = UI/MailPartViewers/Icelandic.lproj/Localizable.strings
trans.it = UI/MailPartViewers/Italian.lproj/Localizable.strings
trans.mk_MK = UI/MailPartViewers/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MailPartViewers/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MailPartViewers/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MailPartViewers/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MailPartViewers/Polish.lproj/Localizable.strings
trans.pt = UI/MailPartViewers/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MailPartViewers/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MailPartViewers/Russian.lproj/Localizable.strings
trans.sk = UI/MailPartViewers/Slovak.lproj/Localizable.strings
trans.sl_SI = UI/MailPartViewers/Slovenian.lproj/Localizable.strings
trans.sv = UI/MailPartViewers/Swedish.lproj/Localizable.strings
trans.uk = UI/MailPartViewers/Ukrainian.lproj/Localizable.strings
trans.zh_TW = UI/MailPartViewers/ChineseTaiwan.lproj/Localizable.strings
+4 -1
View File
@@ -1 +1,4 @@
# compilation settings
ifeq ($(HAS_LIBRARY_ssl),yes)
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
BUNDLE_LIBS += -lcrypto
endif
+1 -1
View File
@@ -101,7 +101,7 @@ static NSArray *asElementArray = nil;
int i, count;
if (!asElementArray)
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil];
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
data = [NSMutableDictionary dictionary];
+181 -49
View File
@@ -48,11 +48,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation NGVCard (ActiveSync)
//
// This function is called for each elements which can be ghosted according to specs.
// https://msdn.microsoft.com/en-us/library/gg650908%28v=exchg.80%29.aspx
//
- (BOOL) _isGhosted: (NSString *) element
inContext: (WOContext *) context
{
NSArray *supportedElements;
supportedElements = [context objectForKey: @"SupportedElements"];
// If the client does not include a Supported element in the initial Sync command request for
// a folder, then all of the elements that can be ghosted are considered not ghosted.
if (!supportedElements)
return NO;
// If the client includes an empty Supported element in the initial Sync command request for
// a folder, then all elements that can be ghosted are considered ghosted.
if (![supportedElements count])
return YES;
// If the client includes a Supported element that contains child elements in the initial
// Sync command request for a folder, then each child element of that Supported element is
// considered not ghosted. All elements that can be ghosted that are not included as child
// elements of the Supported element are considered ghosted.
if (!([supportedElements indexOfObject: element] == NSNotFound))
return YES;
return NO;
}
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{
NSArray *emails, *addresses, *categories, *elements;
CardElement *n, *homeAdr, *workAdr;
NSMutableString *s;
NSMutableString *s, *a;
NSString *url;
id o;
@@ -63,7 +94,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [n flattenedValueAtIndex: 0 forKey: @""]))
[s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
@@ -146,16 +177,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count])
{
homeAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""]))
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [o activeSyncRepresentationInContext: context]];
[a appendString: o];
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", o];
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""]))
[s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""]))
[s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]];
@@ -171,9 +208,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count])
{
workAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""]))
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [o activeSyncRepresentationInContext: context]];
[a appendString: o];
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", o];
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
@@ -217,6 +260,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inContext: (WOContext *) context
{
CardElement *element;
NSMutableArray *addressLines;
id o;
// Contact's note
@@ -226,6 +270,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Categories
if ((o = [theValues objectForKey: @"Categories"]) && [o length])
[self setCategories: o];
else
[[self children] removeObjectsInArray: [self childrenWithTag: @"Categories"]];
// Birthday
if ((o = [theValues objectForKey: @"Birthday"]))
@@ -233,6 +279,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
o = [o calendarDate];
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
}
else if (![self _isGhosted: @"Birthday" inContext: context])
{
[self setBday: @""];
}
//
// Business address information
@@ -244,18 +295,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// BusinessCountry
//
element = [self elementWithTag: @"adr" ofType: @"work"];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessStreet"]
atIndex: 2 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
atIndex: 3 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
atIndex: 4 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
atIndex: 6 forKey: @""];
if ((o = [theValues objectForKey: @"BusinessStreet"]) || ![self _isGhosted: @"BusinessStreet" inContext: context])
{
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
atIndex: 2 forKey: @""];
// Extended address line. If there are more than 2 address lines we add them to the extended address line.
if ([addressLines count] > 1)
{
[addressLines removeObjectAtIndex: 0];
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
atIndex: 1 forKey: @""];
}
}
if ((o = [theValues objectForKey: @"BusinessCity"]) || ![self _isGhosted: @"BusinessCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessState"]) || ![self _isGhosted: @"BusinessState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessPostalCode"]) || ![self _isGhosted: @"BusinessPostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessCountry"]) || ![self _isGhosted: @"BusinessCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
atIndex: 6 forKey: @""];
}
//
// Home address information
@@ -267,35 +348,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// HomeCountry
//
element = [self elementWithTag: @"adr" ofType: @"home"];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeStreet"]
atIndex: 2 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
atIndex: 3 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeState"]
atIndex: 4 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
atIndex: 6 forKey: @""];
if ((o = [theValues objectForKey: @"HomeStreet"]) || ![self _isGhosted: @"HomeStreet" inContext: context])
{
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
atIndex: 2 forKey: @""];
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
if ([addressLines count] > 1)
{
[addressLines removeObjectAtIndex: 0];
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
atIndex: 1 forKey: @""];
}
}
if ((o = [theValues objectForKey: @"HomeCity"]) || ![self _isGhosted: @"HomeCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomeState"]) || ![self _isGhosted: @"HomeState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomePostalCode"]) || ![self _isGhosted: @"HomePostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomeCountry"]) || ![self _isGhosted: @"HomeCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
atIndex: 6 forKey: @""];
}
// Company's name
if ((o = [theValues objectForKey: @"CompanyName"]))
[self setOrg: o units: nil];
else if (![self _isGhosted: @"CompanyName" inContext: context])
[self setOrg: @"" units: nil];
// Department
if ((o = [theValues objectForKey: @"Department"]))
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
else if (![self _isGhosted: @"Department" inContext: context])
[self setOrg: nil units: [NSArray arrayWithObjects:@"",nil]];
// Email addresses
if ((o = [theValues objectForKey: @"Email1Address"]))
if ((o = [theValues objectForKey: @"Email1Address"]) || ![self _isGhosted: @"Email1Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"work"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
}
if ((o = [theValues objectForKey: @"Email2Address"]))
if ((o = [theValues objectForKey: @"Email2Address"]) || ![self _isGhosted: @"Email2Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"home"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
@@ -303,7 +418,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
// FIXME: revise this when the GUI revamp is done in SOGo
if ((o = [theValues objectForKey: @"Email3Address"]))
if ((o = [theValues objectForKey: @"Email3Address"]) || ![self _isGhosted: @"Email3Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"three"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
@@ -313,45 +428,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// MiddleName
// Suffix (II)
// Title (Mr.)
[self setFn: [theValues objectForKey: @"FileAs"]];
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
[self setFn: [theValues objectForKey: @"FileAs"]];
[self setNWithFamily: [theValues objectForKey: @"LastName"]
given: [theValues objectForKey: @"FirstName"]
additional: nil prefixes: nil suffixes: nil];
// IM information
[[self uniqueChildWithTag: @"x-aim"]
setSingleValue: [theValues objectForKey: @"IMAddress"]
forKey: @""];
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
[[self uniqueChildWithTag: @"x-aim"]
setSingleValue: [theValues objectForKey: @"IMAddress"]
forKey: @""];
//
// Phone numbrrs
//
element = [self elementWithTag: @"tel" ofType: @"work"];
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"BusinessPhoneNumber"]) || ![self _isGhosted: @"BusinessPhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"work"];
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"home"];
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"HomePhoneNumber"]) || ![self _isGhosted: @"HomePhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"home"];
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"cell"];
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"MobilePhoneNumber"]) || ![self _isGhosted: @"MobilePhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"cell"];
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"fax"];
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"BusinessFaxNumber"]) || ![self _isGhosted: @"BusinessFaxNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"fax"];
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"pager"];
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"PagerNumber"]) || ![self _isGhosted: @"PagerNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"pager"];
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
}
// Job's title
if ((o = [theValues objectForKey: @"JobTitle"]))
if ((o = [theValues objectForKey: @"JobTitle"]) || ![self _isGhosted: @"JobTitle" inContext: context])
[self setTitle: o];
// WebPage (work)
if ((o = [theValues objectForKey: @"WebPage"]))
if ((o = [theValues objectForKey: @"WebPage"]) || ![self _isGhosted: @"WebPage" inContext: context])
[[self elementWithTag: @"url" ofType: @"work"]
setSingleValue: o forKey: @""];
if ((o = [theValues objectForKey: @"NickName"]))
if ((o = [theValues objectForKey: @"NickName"]) || ![self _isGhosted: @"NickName" inContext: context])
[self setNickname: o];
if ((o = [theValues objectForKey: @"Picture"]))
+5 -3
View File
@@ -131,10 +131,12 @@ static NSArray *easCommandParameters = nil;
}
}
self = [[[NSString alloc] initWithBytes:buf length:(j) * sizeof(wchar_t) encoding:NSUTF32StringEncoding] autorelease];
self = [[NSString alloc] initWithBytesNoCopy: buf
length: (j*sizeof(wchar_t))
encoding: NSUTF32StringEncoding
freeWhenDone: YES];
if (buf) free(buf);
return self;
return [self autorelease];
}
- (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType
File diff suppressed because it is too large. Load diff
+11 -1
View File
@@ -1,6 +1,6 @@
/*
Copyright (c) 2014, Inverse inc.
Copyright (c) 2014-2015, Inverse inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,15 +31,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncConstants.h"
@class NSCalendarDate;
@class NSException;
@class NSMutableDictionary;
@class NSURL;
@class NSNumber;
@interface SOGoActiveSyncDispatcher : NSObject
{
NSURL *folderTableURL;
NSDictionary *imapFolderGUIDS;
id context;
NSNumber *syncRequest;
BOOL debugOn;
}
- (NSMutableDictionary *) globalMetadataForDevice;
- (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
+354 -78
View File
@@ -30,8 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncDispatcher.h"
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSCalendarDate.h>
#if GNUSTEP_BASE_MINOR_VERSION >= 21
#import <Foundation/NSLocale.h>
#endif
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSURL.h>
@@ -134,6 +138,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
#ifdef HAVE_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#endif
@interface SOGoActiveSyncDispatcher (Sync)
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
@@ -143,20 +153,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher
static BOOL debugOn = NO;
- (id) init
{
[super init];
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
folderTableURL = nil;
imapFolderGUIDS = nil;
syncRequest = nil;
return self;
}
- (void) dealloc
{
RELEASE(folderTableURL);
RELEASE(imapFolderGUIDS);
RELEASE(syncRequest);
[super dealloc];
}
@@ -169,16 +181,16 @@ static BOOL debugOn = NO;
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
[[o properties] removeAllObjects];
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
[[o properties] setObject: theSyncKey
forKey: @"FolderSyncKey"];
[o save];
}
- (NSMutableDictionary *) _globalMetadataForDevice
- (NSMutableDictionary *) globalMetadataForDevice
{
SOGoCacheGCSObject *o;
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
@@ -202,7 +214,7 @@ static BOOL debugOn = NO;
if (theFilter)
{
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setObjectType: ActiveSyncFolderCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
@@ -229,23 +241,27 @@ static BOOL debugOn = NO;
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
SOGoUserFolder *userFolder;
NSDictionary *imapGUIDs;
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
// Get the GUID of the IMAP folder
imapGUIDs = [accountFolder imapFolderGUIDs];
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
if (!imapFolderGUIDS)
{
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
// Get the GUID of the IMAP folder
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
[imapFolderGUIDS retain];
}
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
}
return theIdToTranslate;
}
//
//
//
@@ -691,26 +707,25 @@ static BOOL debugOn = NO;
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType;
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
NSMutableDictionary *cachedGUIDs, *metadata;
NSMutableArray *folders, *processedFolders;
NSDictionary *folderMetadata, *imapGUIDs;
NSArray *allFoldersMetadata, *allKeys;
NSMutableDictionary *cachedGUIDs, *metadata;
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
NSMutableString *s, *commands;
SOGoUserFolder *userFolder;
NSMutableArray *folders;
SoSecurityManager *sm;
SOGoCacheGCSObject *o;
id currentFolder;
NSData *d;
int status, command_count, i, type, fi, count;
BOOL first_sync;
sm = [SoSecurityManager sharedSecurityManager];
metadata = [self _globalMetadataForDevice];
metadata = [self globalMetadataForDevice];
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
s = [NSMutableString string];
@@ -719,6 +734,8 @@ static BOOL debugOn = NO;
command_count = 0;
commands = [NSMutableString string];
processedFolders = [NSMutableArray array];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
@@ -727,7 +744,7 @@ static BOOL debugOn = NO;
first_sync = YES;
syncKey = @"1";
}
else if (![syncKey isEqualToString: [metadata objectForKey: @"FolderSyncKey"]])
else if (![metadata objectForKey: @"FolderSyncKey"])
{
// Synchronization key mismatch or invalid synchronization key
//NSLog(@"FolderSync syncKey mismatch %@ <> %@", syncKey, metadata);
@@ -822,32 +839,38 @@ static BOOL debugOn = NO;
}
else
{
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
folderType = @"Calendar";
else
folderType = @"Contacts";
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
folderType = @"Calendar";
else
folderType = @"Contacts";
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
// remove the folder from device if it doesn't exists or it has not the proper permissions
if (!currentFolder ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: currentFolder
inContext: context])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
command_count++;
[o destroy];
}
}
}
}
// We skip personal GCS folders - we always want to synchronize these
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
[[currentFolder nameInContainer] isEqualToString: @"personal"])
continue;
// Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions
if (!currentFolder ||
![currentFolder synchronize] ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: currentFolder
inContext: context])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
command_count++;
[o destroy];
}
}
}
}
// Handle addition and changes
for (i = 0; i < [allFoldersMetadata count]; i++)
@@ -862,7 +885,13 @@ static BOOL debugOn = NO;
serverId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: nameInCache] substringFromIndex: 6]];
name = [folderMetadata objectForKey: @"displayName"];
// avoid duplicate folders if folder is returned by different imap namespaces
if ([processedFolders indexOfObject: serverId] == NSNotFound)
[processedFolders addObject: serverId];
else
continue;
if ([name hasPrefix: @"/"])
name = [name substringFromIndex: 1];
@@ -874,7 +903,10 @@ static BOOL debugOn = NO;
if ([folderMetadata objectForKey: @"parent"])
{
parentId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: [NSString stringWithFormat: @"folder%@", [[folderMetadata objectForKey: @"parent"] substringFromIndex: 1]]] substringFromIndex: 6]];
// make sure that parent of main-folders is always 0
if (type == 12)
parentId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: [NSString stringWithFormat: @"folder%@", [[folderMetadata objectForKey: @"parent"] substringFromIndex: 1]]] substringFromIndex: 6]];
name = [[name pathComponents] lastObject];
}
@@ -926,7 +958,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
[o save];
command_count++;
@@ -935,17 +969,29 @@ static BOOL debugOn = NO;
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
[folders autorelease];
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
// Inside this loop we remove all the folder without write/delete permissions
// We remove all the folders that aren't GCS-ones, that we don't want to synchronize and
// the ones without write/delete permissions.
count = [folders count]-1;
for (; count >= 0; count--)
{
if ([sm validatePermission: SoPerm_DeleteObjects
onObject: [folders objectAtIndex: count]
currentFolder = [folders objectAtIndex: count];
// We skip personal GCS folders - we always want to synchronize these
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
[[currentFolder nameInContainer] isEqualToString: @"personal"])
continue;
if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] ||
![currentFolder synchronize] ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: [folders objectAtIndex: count]
onObject: currentFolder
inContext: context])
{
[folders removeObjectAtIndex: count];
@@ -953,7 +999,6 @@ static BOOL debugOn = NO;
}
count = [folders count]-1;
NSString *operation;
for (fi = 0; fi <= count ; fi++)
{
@@ -1011,7 +1056,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
}
[o save];
@@ -1034,7 +1081,9 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
}
[o save];
@@ -1193,7 +1242,7 @@ static BOOL debugOn = NO;
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
count = [allMessages count];
@@ -1243,7 +1292,7 @@ static BOOL debugOn = NO;
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId;
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
NSMutableString *s;
NSArray *fetchRequests;
id aFetch;
@@ -1356,6 +1405,8 @@ static BOOL debugOn = NO;
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
[context setObject: mimeSupport forKey: @"MIMESupport"];
currentCollection = [self collectionFromId: realCollectionId type: folderType];
@@ -2013,13 +2064,13 @@ static BOOL debugOn = NO;
if ([foldersWithChanges count])
{
[self logWithFormat: @"Change detected, we push the content."];
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
status = 2;
break;
}
else
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
sleep(internalInterval);
}
}
@@ -2083,6 +2134,123 @@ static BOOL debugOn = NO;
[theResponse setContent: d];
}
//
//
//
#ifdef HAVE_OPENSSL
- (unsigned int) validateCert: (NSString *) theCert
{
NSData *d;
const unsigned char *data;
X509_STORE_CTX *ctx;
X509_LOOKUP *lookup;
X509_STORE *store;
X509 *cert;
BOOL success;
size_t len;
int rc;
success = NO;
d = [theCert dataByDecodingBase64];
data = (unsigned char *)[d bytes];
len = [d length];
cert = d2i_X509(NULL, &data, len);
if (!cert)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
return 17;
}
store = X509_STORE_new();
OpenSSL_add_all_algorithms();
if (store)
{
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
if (lookup)
{
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
if (lookup)
{
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
ERR_clear_error();
success = YES;
}
}
}
if (!success)
{
if (store)
{
X509_STORE_free(store);
store = NULL;
}
}
ctx = X509_STORE_CTX_new();
if (!ctx)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
return 17;
}
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
X509_STORE_CTX_free(ctx);
return 17;
}
rc = X509_verify_cert(ctx);
X509_STORE_CTX_free(ctx);
X509_free(cert);
if (rc)
{
return 1;
}
else
{
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
return 17;
}
}
#else
- (unsigned int) validateCert: (NSString *) theCert
{
return 17;
}
#endif
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSMutableString *s;
NSString *cert;
NSData *d;
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<ValidateCert xmlns=\"ValidateCert:\">"];
[s appendString: @"<Status>1</Status><Certificate>"];
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
[s appendString: @"</Certificate></ValidateCert>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
}
//
// <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
@@ -2399,39 +2567,100 @@ static BOOL debugOn = NO;
NGMimeMessageParser *parser;
NGMimeMessage *message;
NSException *error;
NSData *data;
NGMutableHashMap *map;
NGMimeMessage *messageToSend;
NGMimeMessageGenerator *generator;
NSMutableData *data;
NSData *new_from_header;
NSDictionary *identity;
NSString *fullName, *email;
const char *bytes;
int i, e, len;
BOOL found_header;
// We get the mail's data
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
// We extract the recipients
parser = [[NGMimeMessageParser alloc] init];
message = [parser parsePartFromData: data];
RELEASE(parser);
map = [NGHashMap hashMapWithDictionary: [message headers]];
identity = [[context activeUser] primaryIdentity];
fullName = [identity objectForKey: @"fullName"];
email = [identity objectForKey: @"email"];
if ([fullName length])
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"];
new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", [fullName asQPSubjectString: @"utf-8"], email] dataUsingEncoding:NSUTF8StringEncoding];
else
[map setObject: email forKey: @"from"];
new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding:NSUTF8StringEncoding];
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
bytes = [data bytes];
len = [data length];
i = 0;
found_header = NO;
[messageToSend setBody: [message body]];
// Search for the from-header
while (i < len)
{
if (i == 0 &&
(*bytes == 'f' || *bytes == 'F') &&
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
(*(bytes+4) == ':'))
{
found_header = YES;
break;
}
if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
(*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
(*(bytes+6) == ':'))
{
found_header = YES;
i = i + 2; // \r\n
bytes = bytes + 2;
break;
}
bytes++;
i++;
}
// We search for the first \r\n AFTER the From: header to get the length of the string to replace.
e = i;
while (e < len)
{
if ((*bytes == '\r') && (*(bytes+1) == '\n'))
{
e = e + 2;
break;
}
bytes++;
e++;
}
// Update/Add the From header in the MIMEBody of the SendMail request.
// Any other way to modify the mail body would break s/mime emails.
if (found_header)
{
// Change the From header
[data replaceBytesInRange: NSMakeRange(i, (NSUInteger)(e-i))
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
else
{
// Add a From header
[data replaceBytesInRange: NSMakeRange(0, 0)
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
data = [generator generateMimeFromPart: messageToSend];
error = [self _sendMail: data
recipients: [message allRecipients]
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
@@ -2565,7 +2794,7 @@ static BOOL debugOn = NO;
NGMimeFileData *fdata;
NSException *error;
NSArray *attachmentKeys;
NSMutableArray *attachments;
NSMutableArray *attachments, *references;
id body, bodyFromSmartForward, htmlPart, textPart;
NSString *fullName, *email, *charset, *s;
@@ -2604,7 +2833,30 @@ static BOOL debugOn = NO;
[map setObject: email forKey: @"from"];
if ([mailObject messageId])
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
{
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
// If there is no References: header, initialize it with In-Reply-To.
if ([mailObject inReplyTo] && ![references count])
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
if ([references count] > 0)
{
// If there are more than ten identifiers listed, we eliminate the second one.
if ([references count] >= 10)
[references removeObjectAtIndex: 1];
[references addObject: [mailObject messageId]];
[map setObject: [references componentsJoinedByString:@" "] forKey: @"references"];
}
else
{
[map setObject: [mailObject messageId] forKey: @"references"];
}
}
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
body = [[[NGMimeMultipartBody alloc] initWithPart: messageToSend] autorelease];
@@ -2862,6 +3114,7 @@ static BOOL debugOn = NO;
NSAutoreleasePool *pool;
id builder, dom;
SEL aSelector;
id activeUser;
NSString *cmdName, *deviceId;
NSData *d;
@@ -2870,6 +3123,14 @@ static BOOL debugOn = NO;
ASSIGN(context, theContext);
activeUser = [context activeUser];
if (![activeUser canAccessModule: @"ActiveSync"])
{
[(WOResponse *)theResponse setStatus: 403];
[self logWithFormat: @"EAS - Forbidden access for user %@", [activeUser loginInDomain]];
return nil;
}
// Get the device ID, device type and "stash" them
deviceId = [[theRequest uri] deviceId];
[context setObject: deviceId forKey: @"DeviceId"];
@@ -2896,9 +3157,25 @@ static BOOL debugOn = NO;
options: NSCaseInsensitiveSearch].location == NSNotFound)
{
NSString *value;
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]];
#if GNUSTEP_BASE_MINOR_VERSION < 21
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
locale: nil];
#else
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
locale: [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithObjects: @"Jan", @"Feb", @"Mar", @"Apr",
@"May", @"Jun", @"Jul", @"Aug",
@"Sep", @"Oct", @"Nov", @"Dec", nil],
@"NSShortMonthNameArray",
[NSArray arrayWithObjects: @"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
@"Fri", @"Sat", nil],
@"NSShortWeekDayNameArray",
nil]];
#endif
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
}
else
{
@@ -2988,8 +3265,7 @@ static BOOL debugOn = NO;
return nil;
urlString = [[user domainDefaults] folderInfoURL];
parts = [[urlString componentsSeparatedByString: @"/"]
mutableCopy];
parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
[parts autorelease];
if ([parts count] == 5)
{
+151 -38
View File
@@ -77,6 +77,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Appointments/SOGoAptMailNotification.h>
unsigned char strToChar(char a, char b) {
char encoder[3] = {'\0','\0','\0'};
encoder[0] = a;
encoder[1] = b;
return (char) strtol(encoder,NULL,16);
}
@interface NSString (NSStringExtensions)
- (NSData *) decodeFromHexidecimal;
@end
@implementation NSString (NSStringExtensions)
- (NSData *) decodeFromHexidecimal;
{
const char * bytes = [self cStringUsingEncoding: NSUTF8StringEncoding];
NSUInteger length = strlen(bytes);
unsigned char * r = (unsigned char *) malloc(length / 2 + 1);
unsigned char * index = r;
while ((*bytes) && (*(bytes +1))) {
*index = strToChar(*bytes, *(bytes +1));
index++;
bytes+=2;
}
*index = '\0';
NSData * result = [NSData dataWithBytes: r length: length / 2];
free(r);
return result;
}
@end
typedef struct {
uint32_t dwLowDateTime;
uint32_t dwHighDateTime;
@@ -91,7 +126,7 @@ struct GlobalObjectId {
FILETIME CreationTime;
uint8_t X[8];
uint32_t Size;
uint8_t* Data;
uint8_t Data[0];
};
@implementation SOGoMailObject (ActiveSync)
@@ -118,39 +153,40 @@ struct GlobalObjectId {
//
// The GlobalObjId is documented here: http://msdn.microsoft.com/en-us/library/ee160198(v=EXCHG.80).aspx
//
- (NSData *) _computeGlobalObjectIdFromEvent: (iCalEvent *) event
{
NSData *binPrefix, *globalObjectId, *uidAsASCII;
NSString *prefix, *uid;
struct GlobalObjectId newGlobalId;
struct GlobalObjectId *newGlobalId;
const char *bytes;
uid = [event uid];
uidAsASCII = [uid decodeFromHexidecimal];
newGlobalId = (struct GlobalObjectId*)calloc(sizeof(uint8_t), sizeof(struct GlobalObjectId) + 0x0c + [uidAsASCII length]);
prefix = @"040000008200e00074c5b7101a82e008";
// dataPrefix is "vCal-Uid %x01 %x00 %x00 %x00"
uint8_t dataPrefix[] = { 0x76, 0x43, 0x61, 0x6c, 0x2d, 0x55, 0x69, 0x64, 0x01, 0x00, 0x00, 0x00 };
uid = [event uid];
binPrefix = [prefix convertHexStringToBytes];
[binPrefix getBytes: &newGlobalId.ByteArrayID];
[self _setInstanceDate: &newGlobalId
[binPrefix getBytes: &newGlobalId->ByteArrayID];
[self _setInstanceDate: newGlobalId
fromDate: [event recurrenceId]];
uidAsASCII = [uid dataUsingEncoding: NSASCIIStringEncoding];
bytes = [uidAsASCII bytes];
// 0x0c is the size of our dataPrefix
newGlobalId.Size = 0x0c + [uidAsASCII length];
newGlobalId.Data = malloc(newGlobalId.Size * sizeof(uint8_t));
memcpy(newGlobalId.Data, dataPrefix, 0x0c);
memcpy(newGlobalId.Data + 0x0c, bytes, newGlobalId.Size - 0x0c);
newGlobalId->Size = 0x0c + [uidAsASCII length];
memcpy(newGlobalId->Data, dataPrefix, 0x0c);
memcpy(newGlobalId->Data + 0x0c, bytes, newGlobalId->Size - 0x0c);
globalObjectId = [[NSData alloc] initWithBytes: &newGlobalId length: 40 + newGlobalId.Size*sizeof(uint8_t)];
free(newGlobalId.Data);
globalObjectId = [[NSData alloc] initWithBytes: newGlobalId length: 40 + newGlobalId->Size*sizeof(uint8_t)];
free(newGlobalId);
return [globalObjectId autorelease];
}
//
// For debugging purposes...
//
@@ -436,18 +472,46 @@ struct GlobalObjectId {
return d;
}
//
//
//
- (BOOL) _sanitinizeNeeded: (NSArray *) theParts
{
NSString *encoding, *charset;
int i;
for (i = 0; i < [theParts count]; i++)
{
encoding = [[theParts objectAtIndex: i ] objectForKey: @"encoding"];
charset = [[[theParts objectAtIndex: i ] objectForKey: @"parameterList"] objectForKey: @"charset"];
if (encoding && [encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame &&
charset && ![charset caseInsensitiveCompare: @"utf-8"] == NSOrderedSame
&& ![charset caseInsensitiveCompare: @"us-ascii"] == NSOrderedSame)
{
return YES;
}
if ([self _sanitinizeNeeded: [[theParts objectAtIndex: i ] objectForKey: @"parts"]])
return YES;
}
return NO;
}
//
//
//
- (NSData *) _preferredBodyDataUsingType: (int) theType
mimeSupport: (int) theMimeSupport
nativeType: (int *) theNativeType
{
NSString *type, *subtype, *encoding;
NSData *d;
BOOL isSMIME, sanitinizeNeeded;
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
isSMIME = NO;
d = nil;
// We determine the native type
@@ -458,8 +522,14 @@ struct GlobalObjectId {
else if ([type isEqualToString: @"multipart"])
*theNativeType = 4;
if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && theMimeSupport > 0)
{
*theNativeType = 4;
isSMIME = YES;
}
// We get the right part based on the preference
if (theType == 1 || theType == 2)
if ((theType == 1 || theType == 2) && !isSMIME)
{
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
{
@@ -500,12 +570,12 @@ struct GlobalObjectId {
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
}
}
else if (theType == 4)
else if (theType == 4 || isSMIME)
{
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
// We sanitize the content if the content-transfer-encoding is 8bit and charset is not utf-8 or us-ascii.
sanitinizeNeeded = [self _sanitinizeNeeded: [NSArray arrayWithObject: [self bodyStructure]]];
if (sanitinizeNeeded && !isSMIME)
d = [self _sanitizedMIMEMessage];
else
d = [self content];
@@ -514,6 +584,28 @@ struct GlobalObjectId {
return d;
}
- (NSString *) _getNormalizedSubject
{
NSString *subject;
NSUInteger colIdx;
NSString *stringValue;
subject = [[self subject] decodedHeader];
colIdx = [subject rangeOfString: @":" options:NSBackwardsSearch].location;
if (colIdx != NSNotFound && colIdx + 1 < [subject length])
stringValue = [[subject substringFromIndex: colIdx + 1] stringByTrimmingLeadSpaces];
else
stringValue = subject;
if (!stringValue)
stringValue = @"";
return stringValue;
}
//
//
//
@@ -598,16 +690,18 @@ struct GlobalObjectId {
{
NSData *d, *globalObjId;
NSArray *attachmentKeys;
NSMutableString *s;
uint32_t v;
NSString *p;
id value;
iCalCalendar *calendar;
NSString *p, *subtype;
NSMutableString *s;
id value;
int preferredBodyType, nativeBodyType;
int preferredBodyType, mimeSupport, nativeBodyType;
uint32_t v;
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
s = [NSMutableString string];
@@ -627,7 +721,7 @@ struct GlobalObjectId {
if (value)
{
[s appendFormat: @"<Subject xmlns=\"Email:\">%@</Subject>", [value activeSyncRepresentationInContext: context]];
[s appendFormat: @"<ThreadTopic xmlns=\"Email:\">%@</ThreadTopic>", [value activeSyncRepresentationInContext: context]];
[s appendFormat: @"<ThreadTopic xmlns=\"Email:\">%@</ThreadTopic>", [[self _getNormalizedSubject] activeSyncRepresentationInContext: context]];
}
// DateReceived
@@ -804,7 +898,12 @@ struct GlobalObjectId {
else
{
// MesssageClass and ContentClass
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
if ([subtype isEqualToString: @"signed"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME.MultipartSigned"];
else if ([subtype isEqualToString: @"pkcs7-mime"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME"];
else
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
}
@@ -818,10 +917,8 @@ struct GlobalObjectId {
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
// Body - namespace 17
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
nativeBodyType = 1;
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
if (calendar && !d)
{
@@ -923,9 +1020,12 @@ struct GlobalObjectId {
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
// Set the correct type if client requested text/html but we got text/plain
// Set the correct type if client requested text/html but we got text/plain.
// For s/mime mails type is always 4 if mimeSupport is 1 or 2.
if (preferredBodyType == 2 && nativeBodyType == 1)
[s appendString: @"<Type>1</Type>"];
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
[s appendString: @"<Type>4</Type>"];
else
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
@@ -943,7 +1043,8 @@ struct GlobalObjectId {
// Attachments -namespace 16
attachmentKeys = [self fetchFileAttachmentKeys];
if ([attachmentKeys count])
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
{
int i;
@@ -1013,7 +1114,19 @@ struct GlobalObjectId {
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
{
if ([self inReplyTo])
id value;
NSString *reference;
value = [[self mailHeaders] objectForKey: @"references"];
if ([value isKindOfClass: [NSArray class]])
reference = [[[value objectAtIndex: 0] componentsSeparatedByString: @" "] objectAtIndex: 0];
else
reference = [[value componentsSeparatedByString: @" "] objectAtIndex: 0];
if ([reference length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
else if ([self inReplyTo])
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self inReplyTo] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
else if ([self messageId])
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self messageId] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
+1
View File
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@interface SOGoSyncCacheObject : NSObject
{
@public
id _uid;
id _sequence;
}
+22 -13
View File
@@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSNull.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
static Class NSNullK;
@implementation SOGoSyncCacheObject
+ (void) initialize
{
NSNullK = [NSNull class];
}
- (id) init
{
if ((self = [super init]))
@@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return self;
}
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
{
id o;
o = [[self alloc] init];
[o setUID: theUID];
[o setSequence: theSequence];
[o setUID: [NSNumber numberWithInt: [theUID intValue]]];
[o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
return [o autorelease];
}
@@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) uid
{
return _uid;
return [_uid description];
}
- (void) setUID: (id) theUID
@@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) sequence
{
return _sequence;
return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
}
- (void) setSequence: (id) theSequence
@@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
{
return [[self uid] compare: [theSyncCacheObject uid]];
return [self->_uid compare: theSyncCacheObject->_uid];
}
//
@@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
{
if ([[self sequence] isEqual: [NSNull null]] &&
[[theSyncCacheObject sequence] isEqual: [NSNull null]])
if ([self->_sequence isEqual: [NSNull null]] &&
[theSyncCacheObject->_sequence isEqual: [NSNull null]])
return [self compareUID: theSyncCacheObject];
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]])
if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedDescending;
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]])
if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedAscending;
// Must check this here, to avoid comparing NSNull objects
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame)
if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
return [self compareUID: theSyncCacheObject];
return [[self sequence] compare: [theSyncCacheObject sequence]];
return [self->_sequence compare: theSyncCacheObject->_sequence];
}
- (NSString *) description
@@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
@end
+1 -1
View File
@@ -58,7 +58,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta];
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
}
return s;
+276 -13
View File
@@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSTimeZone.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
@@ -45,11 +47,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/NSCalendarDate+NGCards.h>
#import <NGCards/NSString+NGCards.h>
#import <NGCards/iCalCalendar.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/WORequest+SOGo.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
#include "iCalAlarm+ActiveSync.h"
#include "iCalRecurrenceRule+ActiveSync.h"
@@ -98,10 +105,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else if ([self created])
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]];
// Timezone
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
if ([self startDate])
{
if ([self isAllDay])
if ([self isAllDay] && !tz)
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -114,7 +124,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
if ([self endDate])
{
if ([self isAllDay])
if ([self isAllDay] && !tz)
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -124,9 +134,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
// Timezone
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
if (!tz)
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
@@ -226,9 +233,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx
if ([[self uid] length])
if (![self recurrenceId] && [[self uid] length])
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
// Sensitivity
if ([[self accessClass] isEqualToString: @"PRIVATE"])
v = 2;
@@ -251,7 +258,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"</Categories>"];
}
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
// TODO: improve this to handle more alarm types
if ([self hasAlarms])
@@ -265,7 +271,74 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Recurrence rules
if ([self isRecurrent])
{
NSMutableArray *components, *exdates;
iCalEvent *current_component;
NSString *recurrence_id;
unsigned int count, max, i;
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
components = [NSMutableArray arrayWithArray: [[self parent] events]];
max = [components count];
if (max > 1 || [self hasExceptionDates])
{
exdates = [NSMutableArray arrayWithArray: [self exceptionDates]];
[s appendString: @"<Exceptions xmlns=\"Calendar:\">"];
for (count = 1; count < max; count++)
{
current_component = [components objectAtIndex: count] ;
if ([self isAllDay])
{
recurrence_id = [NSString stringWithFormat: @"%@",
[[[current_component recurrenceId] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: -[userTimeZone secondsFromGMTForDate:
[current_component recurrenceId]]]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
else
{
recurrence_id = [NSString stringWithFormat: @"%@", [[current_component recurrenceId]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
[s appendString: @"<Exception>"];
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
[s appendFormat: @"%@", [current_component activeSyncRepresentationInContext: context]];
[s appendString: @"</Exception>"];
}
for (i = 0; i < [exdates count]; i++)
{
[s appendString: @"<Exception>"];
[s appendString: @"<Exception_Deleted>1</Exception_Deleted>"];
if ([self isAllDay])
{
recurrence_id = [NSString stringWithFormat: @"%@",
[[[[exdates objectAtIndex: i] asCalendarDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: -[userTimeZone secondsFromGMTForDate:
[[exdates objectAtIndex: i] asCalendarDate]]]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
else
{
recurrence_id = [NSString stringWithFormat: @"%@", [[[exdates objectAtIndex: i] asCalendarDate]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
[s appendString: @"</Exception>"];
}
[s appendString: @"</Exceptions>"];
}
}
// Comment
@@ -285,13 +358,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
}
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
if (![self recurrenceId])
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
return s;
}
@@ -337,13 +411,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) takeActiveSyncValues: (NSDictionary *) theValues
inContext: (WOContext *) context
{
iCalDateTime *start, *end;
iCalDateTime *start, *end; //, *oldstart;
NSCalendarDate *oldstart;
NSTimeZone *userTimeZone;
iCalTimeZone *tz;
id o;
int deltasecs;
BOOL isAllDay;
NSMutableArray *occurences;
occurences = [NSMutableArray arrayWithArray: [[self parent] events]];
if ((o = [theValues objectForKey: @"UID"]))
[self setUid: o];
@@ -356,6 +436,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
isAllDay = YES;
}
else if ([occurences count] && !([theValues objectForKey: @"AllDayEvent"]))
{
// If the occurence has no AllDay tag use it from master.
isAllDay = [[occurences objectAtIndex: 0] isAllDay];
}
//
// 0- free, 1- tentative, 2- busy and 3- out of office
@@ -422,6 +507,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
o = [o calendarDate];
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
oldstart = [start dateTime];
[start setTimeZone: tz];
if (isAllDay)
@@ -433,6 +519,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[start setDateTime: o];
}
// Calculate delta if start date has been changed.
if (oldstart)
deltasecs = [[start dateTime ] timeIntervalSinceDate: oldstart] * -1;
else
deltasecs = 0;
}
if ((o = [theValues objectForKey: @"EndTime"]))
@@ -491,6 +583,177 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
RELEASE(rule);
[rule takeActiveSyncValues: o inContext: context];
// Exceptions
if ((o = [theValues objectForKey: @"Exceptions"]) && [o isKindOfClass: [NSArray class]])
{
NSCalendarDate *recurrenceId, *adjustedRecurrenceId, *currentId;
NSMutableArray *exdates, *exceptionstouched;
iCalEvent *currentOccurence;
NSDictionary *exception;
unsigned int i, count, max;
[self removeAllExceptionDates];
exdates = [NSMutableArray array];
exceptionstouched = [NSMutableArray array];
for (i = 0; i < [o count]; i++)
{
exception = [o objectAtIndex: i];
if (![[exception objectForKey: @"Exception_Deleted"] intValue])
{
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
if (isAllDay)
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
// When moving the calendar entry (i.e. changing the startDate) iOS and Android sometimes send a value for
// Exception_StartTime which is not what we expect.
// With this we make sure the recurrenceId is always correct.
//
// iOS problem - If the master is a no-allday-event but the exception is, an invalid Exception_StartTime is in the request.
// e.g. it sends 20150409T040000Z instead of 20150409T060000Z;
// This might be a special case since iPhone doesn't allow an allday-exception on a non-allday-event.
if (!([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay] minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
recurrenceId = [recurrenceId hour:[[start dateTime] hourOfDay] minute:[[start dateTime] minuteOfHour]];
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
adjustedRecurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0 seconds: deltasecs];
// search for an existing occurence and update it
max = [occurences count];
currentOccurence = nil;
count = 1;
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count] ;
currentId = [currentOccurence recurrenceId];
if ([currentId compare: adjustedRecurrenceId] == NSOrderedSame)
{
[exceptionstouched addObject: adjustedRecurrenceId];
[currentOccurence takeActiveSyncValues: exception inContext: context];
break;
}
count++;
currentOccurence = nil;
}
// Create a new occurence if we found none to update.
if (!currentOccurence)
{
iCalDateTime *recid;
currentOccurence = [self mutableCopy];
[currentOccurence removeAllRecurrenceRules];
[currentOccurence removeAllExceptionRules];
[currentOccurence removeAllExceptionDates];
[[self parent] addToEvents: currentOccurence];
[currentOccurence takeActiveSyncValues: exception inContext: context];
recid = (iCalDateTime *)[currentOccurence uniqueChildWithTag: @"recurrence-id"];
if (isAllDay)
[recid setDate: adjustedRecurrenceId];
else
[recid setDateTime: adjustedRecurrenceId];
[exceptionstouched addObject: [recid dateTime]];
}
}
else if ([[exception objectForKey: @"Exception_Deleted"] intValue])
{
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
if (isAllDay)
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0
days: 0 hours: 0 minutes: 0
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
// We add only valid exception dates.
if ([self doesOccurOnDate: recurrenceId] &&
([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay]
minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
{
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
[exdates addObject: [recurrenceId dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: deltasecs]];
}
}
}
// Update exception dates in master event.
max = [exdates count];
if (max > 0)
{
for (count = 0; count < max; count++)
{
if (([exceptionstouched indexOfObject: [exdates objectAtIndex: count]] == NSNotFound))
[self addToExceptionDates: [exdates objectAtIndex: count]];
}
}
// Remove all exceptions included in the request.
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count] ;
currentId = [currentOccurence recurrenceId];
// Delete all occurences which are not touched (modified/added).
if (([exceptionstouched indexOfObject: currentId] == NSNotFound))
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
else if ([self isRecurrent])
{
// We have no exceptions in request but there is a recurrence rule.
// Remove all excpetions and exception dates.
iCalEvent *currentOccurence;
unsigned int count, max;
[self removeAllExceptionDates];
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count];
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
}
else if ([self isRecurrent])
{
// We have no recurrence rule in request.
// Remove all exceptions, exception dates and recurrence rules.
iCalEvent *currentOccurence;
unsigned int count, max;
[self removeAllRecurrenceRules];
[self removeAllExceptionRules];
[self removeAllExceptionDates];
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count];
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
// Organizer - we don't touch the value unless we're the organizer
@@ -498,7 +761,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o]))
{
iCalPerson *person;
person = [iCalPerson elementWithTag: @"organizer"];
[person setEmail: o];
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
+1 -1
View File
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Simple reccurrence rule of type "Monthly"
type = 2;
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
[[[self parent] startDate] dayOfMonth]];
(int)[[[self parent] startDate] dayOfMonth]];
}
}
else if ([self frequency] == iCalRecurrenceFrequenceYearly)
+1 -1
View File
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
+3 -2
View File
@@ -70,8 +70,8 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
<Proxy http://127.0.0.1:20000/SOGo>
## adjust the following to your configuration
RequestHeader set "x-webobjects-server-port" "443"
RequestHeader set "x-webobjects-server-name" "yourhostname"
RequestHeader set "x-webobjects-server-url" "https://yourhostname"
# RequestHeader set "x-webobjects-server-name" "yourhostname"
# RequestHeader set "x-webobjects-server-url" "https://yourhostname"
## When using proxy-side autentication, you need to uncomment and
## adjust the following line:
@@ -90,5 +90,6 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
<IfModule rewrite_module>
RewriteEngine On
RewriteRule ^/.well-known/caldav/?$ /SOGo/dav [R=301]
RewriteRule ^/.well-known/carddav/?$ /SOGo/dav [R=301]
</IfModule>
+4133
View File
File diff suppressed because it is too large. Load diff
+9 -7
View File
@@ -1,5 +1,7 @@
Installation and Configuration Guide
====================================
:toc: left
:icons: font
////
@@ -54,7 +56,7 @@ at http://www.sogo.nu/
Architecture and Compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
image::images/architecture.png[System Architecture]
image::images/architecture.png[System Architecture, 400, 964]
Standard protocols such as CalDAV, CardDAV, GroupDAV, HTTP, IMAP and
SMTP are used to communicate with the SOGo platform or its
@@ -170,7 +172,7 @@ supported by SOGo:
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
* Community ENTerprise Operating System (CentOS) 5, 6 and 7
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie)
* Ubuntu 10.04 (Lucid) to 14.04 (Trusty)
* Ubuntu 12.04 (Precise) to 14.04 (Trusty)
Make sure the required components are started automatically at boot time
and that they are running before proceeding with the SOGo configuration.
@@ -179,7 +181,7 @@ standard distribution. For example, if you are using Red Hat Enterprise
Linux 5, you have to be subscribed to the Red Hat Network before
continuing with the SOGo software installation.
This document covers the installation of SOGo under RHEL 6.
NOTE: This document covers the installation of SOGo under RHEL 6.
For installation instructions on Debian and Ubuntu, please refer
directly to the SOGo website at http://www.sogo.nu/.
@@ -294,7 +296,7 @@ associated to a domain is limited to access only the users data from the
same domain. Consequently, the configuration parameters of SOGo are
defined on three levels:
image::images/preferences-hierarchy.png[Preferences Hierarchy]
image::images/preferences-hierarchy.png[Preferences Hierarchy, 400, 400]
Each level inherits the preferences of the parent level. Therefore,
domain preferences define the defaults values of the user preferences,
@@ -1035,8 +1037,8 @@ repository
|ModulesConstraints (optional)
|Limits the access of any module through a constraint based on an LDAP
attribute; must be a dictionary with keys `Mail`, and/or `Calendar`, for
example:
attribute; must be a dictionary with keys `Mail`, and/or `Calendar`,
and/or `ActiveSync` for example:
----
ModulesConstraints = {
@@ -1125,7 +1127,7 @@ fax number, one could map it to the _facsimiletelephonenumber_ attribute
like this:
----
mapping = \{
mapping = {
  facsimiletelephonenumber = ("fax", "facsimiletelephonenumber");
};
----
@@ -233,6 +233,7 @@ Installation
This section will guide you through the installation of the native
Microsoft Outlook compatibility layer SOGo offers.
////
Red Hat Enterprise Linux v6 x86_64
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -316,7 +317,7 @@ from this guide.
On Ubuntu 12.04, the Samba init scripts need to be modified to
disable the upstart check. For more details, refer to:
https://wiki.samba.org/index.php/Samba4/InitScript
////
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -387,7 +388,7 @@ Run the following commands as root: 
----
samba-tool domain provision --realm=example.com \
--domain=OPENCHANGE \
--domain=EXAMPLE \
--adminpass='%1OpenChange' \
--server-role='domain controller'
@@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
----
You might consider changing the realm and domain used, to suit your
enviroment.
environment.
You might also have to
remove `/etc/samba/smb.conf` prior running this command.
@@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
OpenChange Configuration
~~~~~~~~~~~~~~~~~~~~~~~~
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
First, create the OpenChange MySQL user:
@@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
----
////
On RHEL, make sure SELinux is disabled:
setenforce 0
////
Next, you can start Samba using the usual command:
@@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
While HTTPS is not required to access this service, it is strongly
recommended.
////
On RHEL-based distributions, the apache configuration required by these
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
`/etc/httpd/conf.d/rpcproxy.conf`.
////
For Debian-based distributions, these files can be found
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
@@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
a2enconf ocsmanager
a2enconf rpcproxy
////
On RHEL-based distributions, make sure the `LoadModule` directive is
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
////
The _reqtimeout_ apache module is known to cause problems when using the
default configuration shipped with Debian-based systems. On such
@@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
a2dismod reqtimeout
You should now restart the Apache service and make sure it will start on
boot. On RHEL-based distributions, do:
boot.
////
On RHEL-based distributions, do:
chkconfig httpd on && /etc/init.d/httpd restart
////
On Debian-based distributions, do:
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
@@ -809,11 +818,10 @@ can be ignored for now. This feature is currently not supported.
remove any data associated with the user from the SOGo server and
recreate a Microsoft Outlook profile.
To remove any data associated to a user, use
the `openchange_user_cleanup` script distributed with SOGo. The script
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
RHEL).
the `openchange_user_cleanup` script distributed with OpenChange. The script
can be found in `/usr/share/openchange/`.
To reset a user, run the script as root:
`python openchange_user_cleanup username`. See the usage output for additional options.
`openchange_user_cleanup username`. See the usage output for additional options.
* The "Out of Office Assistant" will not currently work. This feature
has not been implemented.
* Creating folders below INBOX (when not normally permitted by the IMAP
+3 -3
View File
@@ -1,7 +1,7 @@
<!-- TODO have the build system take care of this -->
<releaseinfo>Version 2.3.0 - June 2015</releaseinfo>
<subtitle>for version 2.3.0</subtitle>
<date>2015-06-01</date>
<releaseinfo>Version 2.3.4 - December 2015</releaseinfo>
<subtitle>for version 2.3.4</subtitle>
<date>2015-12-15</date>
<legalnotice>
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
@@ -13,6 +13,6 @@
// TODO have the build system take care of this
:release_version: 2.3.0
:release_version: 2.3.4
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
+1 -1
View File
@@ -267,7 +267,7 @@
NSObject <DOMNodeList> *list;
NSObject <DOMNode> *valueNode;
NSArray *elements;
NSString *property, *match;
NSString *property=nil, *match=nil;
list = [searchElement getElementsByTagName: @"prop"];
if ([list length])
+101 -1
View File
@@ -1,10 +1,102 @@
2.3.1 (2015-06-XX)
2.3.4 (2015-12-15)
------------------
New features
- initial support for EAS calendar exceptions
Enhancements
- limit the maximum width of toolbar buttons (#3381)
Bug fixes
- JavaScript exception when printing events from calendars with no assigned color (#3203)
- EAS fix for wrong charset being used (#3392)
- EAS fix on qp-encoded subjects (#3390)
- correctly handle all-day event exceptions when the master event changes
- prevent characters in calendar component UID causing issues during import process
- avoid duplicating attendees when accepting event using a different identity over CalDAV
2.3.3a (2015-11-18)
-------------------
Bug fixes
- expanded mail folders list is not saved (#3386)
- cleanup translations
2.3.3 (2015-11-11)
------------------
New features
- initial S/MIME support for EAS (#3327)
- now possible to choose which folders to sync over EAS
Enhancements
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS
- support for ghosted elements on contacts over EAS
- added Macedonian (mk_MK) translation - thanks to Miroslav Jovanovic
- added Portuguese (pt) translation - thanks to Eduardo Crispim
Bug fixes
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
- correctly handle the References header over EAS (#3365)
- make sure English is always used when generating Date headers using EAS (#3356)
- don't escape quoted strings during versit generation
- we now return all cards when we receive an empty addressbook-query REPORT
- avoid crash when replying to a mail with no recipients (#3359)
- inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271)
- prevent postal address showing on single line over EAS (#2614)
- display missing events when printing working hours only
- fix corner case making server crash when syncing hard deleted messages when clear offline items was set up (Zentyal)
- avoid infinite Outlook client loops trying to set read flag when it is already set (Zentyal)
- avoid crashing when calendar metadata is missing in the cache (Zentyal)
- fix recurrence pattern event corner case created by Mozilla Thunderbird which made server crash (Zentyal)
- fix corner case that removes attachments on sending messages from Outlook (Zentyal)
- freebusy on web interface works again in multidomain environments (Zentyal)
- fix double creation of folders in Outlook when the folder name starts with a digit (Zentyal)
- avoid crashing Outlook after setting a custom view in a calendar folder (Zentyal)
- handle emails having an attachment as their content
- fixed JavaScript syntax error in attendees editor
- fixed wrong comparison of meta vs. META tag in HTML mails
- fixed popup menu position when moved to the left (#3381)
- fixed dialog position when at the bottom of the window (#2646, #3378)
2.3.2 (2015-09-16)
------------------
Enhancements
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
- improved EAS speed during initial syncing of large mailboxes (#3293)
- updated CKEditor to version 4.5.3
Bug fixes
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
- fixed several sync issues on environments with multiple users (Zentyal)
- folders from other users will no longer appear on your Outlook (Zentyal)
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
- 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)
------------------
Enhancements
- improved EAS speed, especially when fetching big attachments
- now always enforce the organizer's default identity in appointments
- improved the handling of default calendar categories/colors (#3200)
- added support for DeletesAsMoves over EAS
- 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
Bug fixes
- EAS's GetItemEstimate/ItemOperations now support fetching mails and empty folders
@@ -14,6 +106,14 @@ Bug fixes
- properly support big characters in EAS and fix encoding QP EAS error for Outlook (#3082)
- properly encode id of DOM elements in Address Book module (#3239, #3245)
- fixed multi-domain support for sogo-tool backup/restore (#2600)
- fixed data ordering in events list of Calendar module (#3261)
- fixed data ordering in tasks list of Calendar module (#3267)
- Android EAS Lollipop fixes (#3268 and #3269)
- improved EAS email flagging handling (#3140)
- fixed computation of GlobalObjectId (#3235)
- fixed EAS conversation ID issues on BB10 (#3152)
- fixed CR/LF printing in event's description (#3228)
- optimized Calendar module in multidomain configurations
2.3.0 (2015-06-01)
-------------------
+2 -1
View File
@@ -33,8 +33,9 @@
}
- (id) authenticatorInContext: (id) context;
- (MAPIStoreUserContext *) userContext;
- (void) setUserContext: (MAPIStoreUserContext *) newContext;
- (void) cleanup;
@end
+10
View File
@@ -76,6 +76,11 @@ MAPIApplication *MAPIApp = nil;
return NO;
}
- (MAPIStoreUserContext *) userContext
{
return userContext;
}
- (void) setUserContext: (MAPIStoreUserContext *) newContext
{
/* user contexts must not be retained here ad their holder (mapistore)
@@ -88,4 +93,9 @@ MAPIApplication *MAPIApp = nil;
return [userContext authenticator];
}
- (void) cleanup
{
[userContext deactivate];
}
@end
+3
View File
@@ -46,6 +46,9 @@
forURL: (NSURL *) server
forceRenew: (BOOL) renew;
- (NSString *) passwordInContext: (WOContext *) context;
@end
#endif /* MAPISTOREAUTHENTICATOR_H */
+4
View File
@@ -72,4 +72,8 @@
return imapPassword;
}
- (NSString *) passwordInContext: (WOContext *) context
{
return password;
}
@end
+1 -5
View File
@@ -63,19 +63,15 @@
SOGoAppointmentObject *newEntry;
NSString *name;
//[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__];
[[self userContext] activate];
name = [NSString stringWithFormat: @"%@.ics",
[SOGoObject globallyUniqueObjectId]];
newEntry = [SOGoAppointmentObject objectWithName: name
inContainer: sogoObject];
[newEntry setIsNew: YES];
/* the WOContext is required here for resolving notification pages */
[newEntry setContext: [[self userContext] woContext]];
newMessage = [MAPIStoreCalendarMessage mapiStoreObjectWithSOGoObject: newEntry
inContainer: self];
return newMessage;
}
+4 -3
View File
@@ -494,6 +494,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
folder = [sogoObject container];
/* reinstantiate the old sogo object and attach it to self */
[[self userContext] activate];
woContext = [[self userContext] woContext];
if (isNew)
{
@@ -577,7 +578,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
}
}
- (void) save: (TALLOC_CTX *) memCtx
- (void) save: (TALLOC_CTX *) memCtx
{
// iCalCalendar *vCalendar;
// NSCalendarDate *now;
@@ -634,7 +635,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
withActiveUser: activeUser
inMemCtx: memCtx];
[self _updateAttachedEvents];
[[self userContext] activateWithUser: activeUser];
[[self userContext] activate];
[sogoObject updateContentWithCalendar: calendar
fromRequest: nil];
[self updateVersions];
@@ -670,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
return newAttachment;
}
- (int) setReadFlag: (uint8_t) flag
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
return MAPISTORE_SUCCESS;
}
+5 -2
View File
@@ -28,6 +28,7 @@
#import <Contacts/SOGoContactFolders.h>
#import "MAPIApplication.h"
#import "MAPIStoreUserContext.h"
#import "MAPIStoreContactsContext.h"
#import "MAPIStoreContactsMessage.h"
#import "MAPIStoreContactsMessageTable.h"
@@ -58,10 +59,12 @@
SOGoContactGCSEntry *newEntry;
NSString *name;
[[self userContext] activate];
name = [NSString stringWithFormat: @"%@.vcf",
[SOGoObject globallyUniqueObjectId]];
newEntry = [SOGoContactGCSEntry objectWithName: name
inContainer: sogoObject];
inContainer: sogoObject];
[newEntry setIsNew: YES];
newMessage = [MAPIStoreContactsMessage mapiStoreObjectWithSOGoObject: newEntry
inContainer: self];
@@ -100,7 +103,7 @@
rights |= RightsReadItems;
if (rights != 0)
rights |= RoleNone; /* actually "folder visible" */
return rights;
}
+28 -70
View File
@@ -33,7 +33,6 @@
#import <SOGo/SOGoUser.h>
#import "MAPIStoreAttachment.h"
// #import "MAPIStoreAttachmentTable.h"
#import "MAPIStoreFallbackContext.h"
#import "MAPIStoreFolder.h"
#import "MAPIStoreFolderTable.h"
@@ -89,32 +88,31 @@ static NSMutableDictionary *contextClassMapping;
currentClass = [classes objectAtIndex: count];
moduleName = [currentClass MAPIModuleName];
if (moduleName)
{
[contextClassMapping setObject: currentClass
{
[contextClassMapping setObject: currentClass
forKey: moduleName];
NSLog (@" registered class '%@' as handler of '%@' contexts",
NSStringFromClass (currentClass), moduleName);
}
NSLog (@" registered class '%@' as handler of '%@' contexts",
NSStringFromClass (currentClass), moduleName);
}
}
MAPIStoreFallbackContextK = [MAPIStoreFallbackContext class];
}
+ (struct mapistore_contexts_list *) listAllContextsForUser: (NSString *) userName
withIndexing: (struct indexing_context *) indexing
withIndexing: (struct indexing_context *) indexing
inMemCtx: (TALLOC_CTX *) memCtx
{
struct mapistore_contexts_list *list, *current;
NSArray *classes;
Class currentClass;
NSUInteger count, max;
MAPIStoreUserContext *userContext;
list = NULL;
userContext = [MAPIStoreUserContext userContextWithUsername: userName
andTDBIndexing: indexing];
[userContext activateWithUser: [userContext sogoUser]];
// User context is activated on initialization
[MAPIStoreUserContext userContextWithUsername: userName
andTDBIndexing: indexing];
classes = GSObjCAllSubclassesOfClass (self);
max = [classes count];
@@ -122,7 +120,7 @@ static NSMutableDictionary *contextClassMapping;
{
currentClass = [classes objectAtIndex: count];
current = [currentClass listContextsForUser: userName
withIndexing: indexing
withIndexing: indexing
inMemCtx: memCtx];
if (current)
DLIST_CONCATENATE(list, current, void);
@@ -132,7 +130,7 @@ static NSMutableDictionary *contextClassMapping;
}
+ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName
withIndexing: (struct indexing_context *) indexing
withIndexing: (struct indexing_context *) indexing
inMemCtx: (TALLOC_CTX *) memCtx
{
return NULL;
@@ -225,8 +223,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
NSURL *baseURL;
int rc = MAPISTORE_ERR_NOT_FOUND;
//NSLog (@"METHOD '%s' (%d) -- uri: '%s'", __FUNCTION__, __LINE__, newUri);
context = nil;
baseURL = CompleteURLFromMapistoreURI (newUri);
@@ -298,13 +294,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
ASSIGN (userContext,
[MAPIStoreUserContext userContextWithUsername: username
andTDBIndexing: indexing]);
#if 0
mapistore_mgmt_backend_register_user (newConnInfo,
"SOGo",
[username UTF8String]);
#endif
connInfo = newConnInfo;
username = [NSString stringWithUTF8String: newConnInfo->username];
ASSIGN (activeUser, [SOGoUser userWithLogin: username]);
@@ -322,12 +311,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
- (void) dealloc
{
#if 0
mapistore_mgmt_backend_unregister_user ([self connectionInfo], "SOGo",
[[userContext username]
UTF8String]);
#endif
[contextUrl release];
[userContext release];
[containersBag release];
@@ -355,27 +338,16 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
return activeUser;
}
// - (void) logRestriction: (struct mapi_SRestriction *) res
// withState: (MAPIRestrictionState) state
// {
// NSString *resStr;
// resStr = MAPIStringForRestriction (res);
// [self logWithFormat: @"%@ --> %@", resStr, MAPIStringForRestrictionState (state)];
// }
- (int) getPath: (char **) path
ofFMID: (uint64_t) fmid
inMemCtx: (TALLOC_CTX *) memCtx
{
int rc;
NSString *objectURL, *url;
// TDB_DATA key, dbuf;
url = [contextUrl absoluteString];
// FIXME transform percent escapes but not for user part of the url
//stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
//[xxxx stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
objectURL = [[userContext mapping] urlFromID: fmid];
if (objectURL)
{
@@ -396,14 +368,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
else
{
[self errorWithFormat: @"%s: you should *never* get here", __PRETTY_FUNCTION__];
// /* attempt to populate our mapping dict with data from indexing.tdb */
// key.dptr = (unsigned char *) talloc_asprintf (memCtx, "0x%.16llx",
// (long long unsigned int )fmid);
// key.dsize = strlen ((const char *) key.dptr);
// dbuf = tdb_fetch (memCtx->indexing_list->index_ctx->tdb, key);
// talloc_free (key.dptr);
// uri = talloc_strndup (memCtx, (const char *)dbuf.dptr, dbuf.dsize);
*path = NULL;
rc = MAPISTORE_SUCCESS;
}
@@ -426,7 +390,7 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
NSArray *pathComponents;
NSUInteger count, max;
[userContext activateWithUser: activeUser];
[userContext activate];
woContext = [userContext woContext];
[self ensureContextFolder];
@@ -488,7 +452,7 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
/* utils */
- (NSString *) extractChildNameFromURL: (NSString *) objectURL
andFolderURLAt: (NSString **) folderURL;
andFolderURLAt: (NSString **) folderURL;
{
NSString *childKey;
NSRange lastSlash;
@@ -497,15 +461,15 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
if ([objectURL hasSuffix: @"/"])
objectURL = [objectURL substringToIndex: [objectURL length] - 2];
lastSlash = [objectURL rangeOfString: @"/"
options: NSBackwardsSearch];
options: NSBackwardsSearch];
if (lastSlash.location != NSNotFound)
{
slashPtr = NSMaxRange (lastSlash);
childKey = [objectURL substringFromIndex: slashPtr];
if ([childKey length] == 0)
childKey = nil;
childKey = nil;
if (folderURL)
*folderURL = [objectURL substringToIndex: slashPtr];
*folderURL = [objectURL substringToIndex: slashPtr];
}
else
childKey = nil;
@@ -516,11 +480,10 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
- (uint64_t) idForObjectWithKey: (NSString *) key
inFolderURL: (NSString *) folderURL
{
NSString *childURL, *owner;
NSString *childURL;
MAPIStoreMapping *mapping;
uint64_t mappingId;
uint32_t contextId;
void *rootObject;
enum mapistore_error ret;
if (key)
childURL = [NSString stringWithFormat: @"%@%@", folderURL,
@@ -531,17 +494,13 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
mappingId = [mapping idFromURL: childURL];
if (mappingId == NSNotFound)
{
//[self warnWithFormat: @"no id exist yet for '%@', requesting one...",
// childURL];
mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId);
[mapping registerURL: childURL withID: mappingId];
contextId = 0;
mapistore_search_context_by_uri (connInfo->mstore_ctx, [folderURL UTF8String],
&contextId, &rootObject);
owner = [userContext username];
mapistore_indexing_record_add_mid (connInfo->mstore_ctx, contextId,
[owner UTF8String], mappingId);
[self logWithFormat: @"No id exist yet for '%@', requesting one", childURL];
ret = mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId);
if (ret == MAPISTORE_SUCCESS)
[mapping registerURL: childURL withID: mappingId];
else
[self errorWithFormat: @"Error trying to get new folder id (%d): %s",
ret, mapistore_errstr (ret)];
}
return mappingId;
@@ -595,15 +554,14 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri)
memCtx = talloc_zero(NULL, TALLOC_CTX);
newFMIDs = [NSMutableArray arrayWithCapacity: max];
if (mapistore_indexing_get_new_folderIDs (connInfo->mstore_ctx,
memCtx, max, &numbers)
!= MAPISTORE_SUCCESS || numbers->cValues != max)
abort ();
for (count = 0; count < max; count++)
{
newNumber
= [NSString stringWithUnsignedLongLong: numbers->lpui8[count]];
newNumber = [NSString stringWithUnsignedLongLong: numbers->lpui8[count]];
[newFMIDs addObject: newNumber];
}
+14 -8
View File
@@ -161,7 +161,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
else
[dbFolder changePathTo: [NSString stringWithFormat: @"/fallback/%@", pathComponent]
intoNewContainer: nil];
mapping = [self mapping];
if (targetFolder)
@@ -193,6 +193,8 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
SOGoMAPIDBMessage *fsObject;
NSString *newKey;
[[self userContext] activate];
newKey = [NSString stringWithFormat: @"%@.plist",
[SOGoObject globallyUniqueObjectId]];
fsObject = [SOGoMAPIDBMessage objectWithName: newKey
@@ -214,10 +216,13 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
ownerUser = [[self userContext] sogoUser];
if ([[context activeUser] isEqual: ownerUser]
|| [self subscriberCanReadMessages])
keys = [(SOGoCacheGCSFolder *) sogoObject childKeysOfType: MAPIMessageCacheObject
includeDeleted: NO
matchingQualifier: qualifier
andSortOrderings: sortOrderings];
{
[[self userContext] activate];
keys = [(SOGoCacheGCSFolder *) sogoObject childKeysOfType: MAPIMessageCacheObject
includeDeleted: NO
matchingQualifier: qualifier
andSortOrderings: sortOrderings];
}
else
keys = [NSArray array];
@@ -227,6 +232,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
- (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier
andSortOrderings: (NSArray *) sortOrderings
{
[[self userContext] activate];
return [dbFolder childKeysOfType: MAPIFolderCacheObject
includeDeleted: NO
matchingQualifier: qualifier
@@ -262,7 +268,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
if ([date laterDate: fileDate] == fileDate)
{
//[self logWithFormat: @"current date: %@", date];
date = fileDate;
}
}
@@ -326,7 +332,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
rights |= RightsFolderContact;
if (rights != 0)
rights |= RoleNone; /* actually "folder visible" */
return rights;
}
@@ -361,7 +367,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
subscribed to read an open contained messages in order to enable them to
find the "LocalFreebusy" message */
[sogoObject reloadIfNeeded];
displayName = [[sogoObject properties]
objectForKey: MAPIPropertyKey (PidTagDisplayName)];
+162 -7
View File
@@ -22,6 +22,7 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
@@ -34,6 +35,7 @@
#import "MAPIStoreDBFolder.h"
#import "MAPIStoreDBMessage.h"
#import "MAPIStoreTypes.h"
#import "NSData+MAPIStore.h"
#import "NSObject+MAPIStore.h"
#import "NSString+MAPIStore.h"
@@ -43,16 +45,28 @@
@implementation MAPIStoreDBMessage
+ (int) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
+ (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
{
struct SPropTagArray *properties;
NSUInteger count;
enum MAPITAGS faiProperties[] = { 0x68350102, 0x683c0102, 0x683e0102,
0x683f0102, 0x68410003, 0x68420102,
0x68450102, 0x68460003,
// PR_VD_NAME_W, PR_VD_FLAGS, PR_VD_VERSION, PR_VIEW_CLSID
0x7006001F, 0x70030003, 0x70070003, 0x68330048 };
enum MAPITAGS faiProperties[] = {
0x68330048, /* PR_VIEW_CLSID */
0x68350102, /* PR_VIEW_STATE */
0x683c0102,
0x683d0040,
0x683e0102,
0x683f0102, /* PR_VIEW_VIEWTYPE_KEY */
0x68410003,
0x68420102,
0x68450102,
0x68460003,
0x7006001F, /* PR_VD_NAME_W */
0x70030003, /* PR_VD_FLAGS */
0x70070003 /* PR_VD_VERSION */
};
size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
properties = talloc_zero (memCtx, struct SPropTagArray);
@@ -74,6 +88,13 @@
return MAPISTORE_SUCCESS;
}
- (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
{
return [[self class] getAvailableProperties: propertiesP
inMemCtx: memCtx];
}
- (id) initWithSOGoObject: (id) newSOGoObject
inContainer: (MAPIStoreObject *) newContainer
{
@@ -104,6 +125,105 @@
return objectVersion;
}
- (void) _updatePredecessorChangeList
{
BOOL updated;
enum mapistore_error rc;
NSData *currentChangeList, *changeKey;
NSMutableArray *changeKeys;
NSMutableData *newChangeList;
NSUInteger count, len;
struct SizedXid *changes;
struct SPropValue property;
struct SRow aRow;
struct XID *currentChangeKey;
TALLOC_CTX *localMemCtx;
uint32_t nChanges;
localMemCtx = talloc_new (NULL);
if (!localMemCtx)
{
[self errorWithFormat: @"No more memory"];
return;
}
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
if (!currentChangeList)
{
/* Create a new PredecessorChangeList */
len = [changeKey length];
newChangeList = [NSMutableData dataWithCapacity: len + 1];
[newChangeList appendUInt8: len];
[newChangeList appendData: changeKey];
}
else
{
/* Update current predecessor change list with new change key */
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
with: &nChanges];
updated = NO;
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
for (count = 0; count < nChanges && !updated; count++)
{
if (GUID_equal(&changes[count].XID.NameSpaceGuid, &currentChangeKey->NameSpaceGuid))
{
NSData *globCnt, *oldGlobCnt;
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
{
if ([globCnt length] != [oldGlobCnt length])
{
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
abort();
}
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
updated = YES;
}
}
}
/* Serialise it */
changeKeys = [NSMutableArray array];
if (!updated)
[changeKeys addObject: changeKey];
for (count = 0; count < nChanges; count++)
{
changeKey = [NSData dataWithXID: &changes[count].XID];
[changeKeys addObject: changeKey];
}
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
newChangeList = [NSMutableData data];
len = [changeKeys count];
for (count = 0; count < len; count++)
{
changeKey = [changeKeys objectAtIndex: count];
[newChangeList appendUInt8: [changeKey length]];
[newChangeList appendData: changeKey];
}
}
if ([newChangeList length] > 0)
{
property.ulPropTag = PidTagPredecessorChangeList;
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
aRow.cValues = 1;
aRow.lpProps = &property;
rc = [self addPropertiesFromRow: &aRow];
if (rc != MAPISTORE_SUCCESS)
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
}
talloc_free (localMemCtx);
}
//
// FIXME: how this can happen?
//
@@ -166,6 +286,9 @@
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
forKey: @"version"];
/* Update PredecessorChangeList accordingly */
[self _updatePredecessorChangeList];
[self logWithFormat: @"%d props in dict", [properties count]];
[sogoObject save];
@@ -209,4 +332,36 @@
return [sogoObject lastModified];
}
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
/* Modify PidTagMessageFlags from SetMessageReadFlag and
SyncImportReadStateChanges ROPs */
NSNumber *flags;
uint32_t newFlag;
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
if (flags)
{
newFlag = [flags unsignedLongValue];
if (flag & SUPPRESS_RECEIPT)
newFlag |= MSGFLAG_READ;
if (flag & CLEAR_RN_PENDING)
newFlag &= ~MSGFLAG_RN_PENDING;
if (flag & CLEAR_READ_FLAG)
newFlag &= ~MSGFLAG_READ;
if (flag & CLEAR_NRN_PENDING)
newFlag &= ~MSGFLAG_NRN_PENDING;
}
else
{
newFlag = MSGFLAG_READ;
if (flag & CLEAR_READ_FLAG)
newFlag = 0x0;
}
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
return MAPISTORE_SUCCESS;
}
@end
+1
View File
@@ -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;
+52 -37
View File
@@ -120,6 +120,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
folderName = [folderURL host];
userContext = [self userContext];
[userContext activate];
[userContext ensureFolderTableExists];
ASSIGN (dbFolder,
@@ -231,30 +232,19 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
- (id) lookupFolder: (NSString *) folderKey
{
MAPIStoreFolder *childFolder;
MAPIStoreFolder *childFolder = nil;
SOGoFolder *sogoFolder;
WOContext *woContext;
childFolder = nil;
if ([[self folderKeys] containsObject: folderKey])
{
woContext = [[self userContext] woContext];
/* We activate the user for the context using the root folder
context as there are times where the active user is not
matching with the one stored in the application context
and SOGo object is storing cached data with the wrong user */
[[self userContext] activateWithUser: [woContext activeUser]];
sogoFolder = [sogoObject lookupName: folderKey inContext: woContext
[[self userContext] activate];
sogoFolder = [sogoObject lookupName: folderKey
inContext: nil
acquire: NO];
if (sogoFolder && ![sogoFolder isKindOfClass: NSExceptionK])
{
[sogoFolder setContext: woContext];
childFolder = [isa mapiStoreObjectWithSOGoObject: sogoFolder
inContainer: self];
}
childFolder = [isa mapiStoreObjectWithSOGoObject: sogoFolder
inContainer: self];
}
else
childFolder = nil;
return childFolder;
}
@@ -285,6 +275,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
if (messageKey)
{
[[self userContext] activate];
msgObject = [sogoObject lookupName: messageKey
inContext: nil
acquire: NO];
@@ -314,6 +305,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
if (messageKey)
{
[[self userContext] activate];
if ([[self faiMessageKeys] containsObject: messageKey])
{
msgObject = [dbFolder lookupName: messageKey
@@ -546,6 +538,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
context = [self context];
ownerUser = [[self userContext] sogoUser];
[[self userContext] activate];
if ([[context activeUser] isEqual: ownerUser]
|| (!isAssociated && [self subscriberCanCreateMessages]))
@@ -649,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
{
@@ -676,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)
@@ -703,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
{
@@ -712,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);
@@ -733,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];
}
@@ -758,9 +763,9 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
}
else
rc = MAPISTORE_ERR_DENIED;
//talloc_free (memCtx);
return rc;
}
@@ -931,7 +936,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
NSArray *newIDs;
uint64_t idNbr;
bool softDeleted;
baseURL = [self url];
mapping = [self mapping];
@@ -1060,9 +1065,10 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
{
static enum MAPITAGS bannedProps[] = { PR_MID, PR_FID, PR_PARENT_FID,
PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY,
PR_CHANGE_KEY, 0x00000000 };
PR_CHANGE_KEY, PidTagChangeNumber, 0x00000000 };
enum MAPITAGS *currentProp;
NSMutableDictionary *propsCopy;
uint64_t cn;
/* TODO: this should no longer be required once mapistore v2 API is in
place, when we can then do this from -dealloc below */
@@ -1080,6 +1086,12 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
}
[properties addEntriesFromDictionary: propsCopy];
/* Update change number after setting the properties */
cn = [[self context] getNewChangeNumber];
[properties setObject: [NSNumber numberWithUnsignedLongLong: cn]
forKey: MAPIPropertyKey (PidTagChangeNumber)];
[dbFolder save];
}
@@ -1105,6 +1117,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
- (NSArray *) faiMessageKeysMatchingQualifier: (EOQualifier *) qualifier
andSortOrderings: (NSArray *) sortOrderings
{
[[self userContext] activate];
return [dbFolder childKeysOfType: MAPIFAICacheObject
includeDeleted: NO
matchingQualifier: qualifier
@@ -1213,7 +1226,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
/*
Possible values are:
0x00000001 Modify
0x00000002 Read
0x00000004 Delete
@@ -1244,7 +1257,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
access |= 0x10;
if (userIsOwner)
access |= 0x20;
*data = MAPILongValue (memCtx, access);
return MAPISTORE_SUCCESS;
@@ -1273,7 +1286,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
rights |= RightsCreateSubfolders;
if (userIsOwner)
rights |= RightsFolderOwner | RightsFolderContact;
*data = MAPILongValue (memCtx, rights);
return MAPISTORE_SUCCESS;
@@ -1309,7 +1322,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
inMemCtx: (TALLOC_CTX *) memCtx
{
*data = MAPIBoolValue (memCtx, [self supportsSubFolders] && [[self folderKeys] count] > 0);
return MAPISTORE_SUCCESS;
}
@@ -1317,7 +1330,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
inMemCtx: (TALLOC_CTX *) memCtx
{
*data = MAPILongValue (memCtx, [[self folderKeys] count]);
return MAPISTORE_SUCCESS;
}
@@ -1419,7 +1432,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
[dbObject setIsNew: YES];
newMessage = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: dbObject
inContainer: self];
return newMessage;
}
@@ -1428,6 +1441,8 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
MAPIStoreMessage *newMessage;
WOContext *woContext;
[[self userContext] activate];
if (isAssociated)
newMessage = [self _createAssociatedMessage];
else
@@ -1627,7 +1642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
permissionUser = nil;
permissionRoles = nil;
if (currentPermission->PermissionDataFlags == ROW_ADD)
isAdd = YES;
else if (currentPermission->PermissionDataFlags == ROW_MODIFY)
@@ -1778,7 +1793,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
{
[self subclassResponsibility: _cmd];
return nil;
return nil;
}
- (NSArray *) getDeletedKeysFromChangeNumber: (uint64_t) changeNum
-2
View File
@@ -121,7 +121,6 @@
userContext = [MAPIStoreUserContext userContextWithUsername: userName
andTDBIndexing: NULL];
[MAPIApp setUserContext: userContext];
moduleName = [self MAPIModuleName];
parentFolder = [[userContext rootFolders] objectForKey: moduleName];
nameInContainer = nil;
@@ -134,7 +133,6 @@
moduleName, nameInContainer];
else
mapistoreURI = nil;
[MAPIApp setUserContext: nil];
return mapistoreURI;
}
+3 -1
View File
@@ -41,8 +41,10 @@
/* synchronisation */
- (BOOL) synchroniseCache;
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) newChangeKey;
withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl;
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
+194 -18
View File
@@ -75,6 +75,7 @@ static Class NSNumberK;
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
inContainer: dbFolder]);
[versionsMessage setObjectType: MAPIInternalCacheObject];
[versionsMessage reloadIfNeeded];
}
- (void) dealloc
@@ -261,7 +262,6 @@ static Class NSNumberK;
*/
- (void) _setChangeKey: (NSData *) changeKey
forMessageEntry: (NSMutableDictionary *) messageEntry
inChangeListOnly: (BOOL) inChangeListOnly
{
struct XID *xid;
NSString *guid;
@@ -270,19 +270,15 @@ static Class NSNumberK;
NSMutableDictionary *changeList;
xid = [changeKey asXIDInMemCtx: NULL];
guid = [NSString stringWithGUID: &xid->GUID];
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
talloc_free (xid);
if (!inChangeListOnly)
{
/* 1. set change key association */
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
guid, @"GUID",
globCnt, @"LocalId",
nil];
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
}
/* 1. set change key association */
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
globCnt, @"LocalId",
nil];
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
/* 2. append/update predecessor change list */
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
@@ -296,6 +292,77 @@ static Class NSNumberK;
[changeList setObject: globCnt forKey: guid];
}
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
forMessageEntry: (NSMutableDictionary *) messageEntry
withOldChangeKey: (NSData *) oldChangeKey
{
NSData *globCnt, *oldGlobCnt;
NSDictionary *changeKeyDict;
NSString *guid;
NSMutableDictionary *changeList;
struct SizedXid *sizedXIDList;
struct XID xid, *givenChangeKey;
TALLOC_CTX *localMemCtx;
uint32_t i, length;
localMemCtx = talloc_new (NULL);
if (!localMemCtx)
{
[self errorWithFormat: @"No more memory"];
return;
}
if (predecessorChangeList)
{
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
if (!changeList)
{
changeList = [NSMutableDictionary new];
[messageEntry setObject: changeList
forKey: @"PredecessorChangeList"];
[changeList release];
}
if (sizedXIDList) {
for (i = 0; i < length; i++)
{
xid = sizedXIDList[i].XID;
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
oldGlobCnt = [changeList objectForKey: guid];
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
[changeList setObject: globCnt forKey: guid];
}
}
}
if (oldChangeKey)
{
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
if (givenChangeKey) {
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
if (!changeKeyDict ||
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
{
/* The given change key is greater than current one stored in
metadata or it does not exist */
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
globCnt, @"LocalId",
nil]
forKey: @"ChangeKey"];
}
}
}
talloc_free (localMemCtx);
}
- (EOQualifier *) componentQualifier
{
if (!componentQualifier)
@@ -465,8 +532,7 @@ static Class NSNumberK;
// A GLOBCNT structure is a 6-byte global namespace counter,
// we strip the first 2 bytes. The first two bytes is the ReplicaId
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
[self _setChangeKey: changeKey forMessageEntry: messageEntry
inChangeListOnly: NO];
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
}
now = [NSCalendarDate date];
@@ -482,13 +548,123 @@ static Class NSNumberK;
return rc;
}
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer
{
/* Try to synchronise old messages in versions.plist cache using an
specific c_name. It returns a boolean indicating if the
synchronisation was carried out succesfully.
It should be used as last resort, keeping synchroniseCache as the
main sync entry point. */
uint64_t changeNumber;
NSString *changeNumberStr;
NSData *changeKey;
NSNumber *cLastModified, *cDeleted, *cVersion;
EOFetchSpecification *fs;
EOQualifier *searchQualifier, *fetchQualifier;
NSArray *fetchResults;
NSDictionary *result;
NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry;
GCSFolder *ocsFolder;
static NSArray *fields;
[versionsMessage reloadIfNeeded];
currentProperties = [versionsMessage properties];
messages = [currentProperties objectForKey: @"Messages"];
if (!messages)
{
messages = [NSMutableDictionary new];
[currentProperties setObject: messages forKey: @"Messages"];
[messages release];
}
messageEntry = [messages objectForKey: nameInContainer];
if (!messageEntry)
{
/* Fetch the message by its name */
if (!fields)
fields = [[NSArray alloc]
initWithObjects: @"c_name", @"c_version", @"c_lastmodified",
@"c_deleted", nil];
searchQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_name"
operatorSelector: EOQualifierOperatorEqual
value: nameInContainer];
fetchQualifier = [[EOAndQualifier alloc]
initWithQualifiers: searchQualifier,
[self contentComponentQualifier],
nil];
[fetchQualifier autorelease];
[searchQualifier release];
ocsFolder = [sogoObject ocsFolder];
fs = [EOFetchSpecification
fetchSpecificationWithEntityName: [ocsFolder folderName]
qualifier: fetchQualifier
sortOrderings: nil];
fetchResults = [ocsFolder fetchFields: fields
fetchSpecification: fs
ignoreDeleted: NO];
if ([fetchResults count] == 1)
{
result = [fetchResults objectAtIndex: 0];
cLastModified = [result objectForKey: @"c_lastmodified"];
cDeleted = [result objectForKey: @"c_deleted"];
if ([cDeleted isKindOfClass: NSNumberK] && [cDeleted intValue])
cVersion = [NSNumber numberWithInt: -1];
else
cVersion = [result objectForKey: @"c_version"];
changeNumber = [[self context] getNewChangeNumber];
changeNumberStr = [NSString stringWithUnsignedLongLong: changeNumber];
/* Create new message entry in Messages dict */
messageEntry = [NSMutableDictionary new];
[messages setObject: messageEntry forKey: nameInContainer];
[messageEntry release];
/* Store cLastModified, cVersion and the change number */
[messageEntry setObject: cLastModified forKey: @"c_lastmodified"];
[messageEntry setObject: cVersion forKey: @"c_version"];
[messageEntry setObject: changeNumberStr forKey: @"version"];
/* Store the change key */
changeKey = [self getReplicaKeyFromGlobCnt: changeNumber >> 16];
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
/* Store the changeNumber -> cLastModified mapping */
mapping = [currentProperties objectForKey: @"VersionMapping"];
if (!mapping)
{
mapping = [NSMutableDictionary new];
[currentProperties setObject: mapping forKey: @"VersionMapping"];
[mapping release];
}
[mapping setObject: cLastModified forKey: changeNumberStr];
/* Save the message */
[versionsMessage save];
return YES;
}
else
return NO;
}
/* If message entry exists, then synchroniseCache did its job */
return YES;
}
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) newChangeKey
withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl
{
NSMutableDictionary *messages, *messageEntry;
[self synchroniseCache];
if (newChangeKey)
if (oldChangeKey || pcl)
{
messages = [[versionsMessage properties] objectForKey: @"Messages"];
messageEntry = [messages objectForKey: messageKey];
@@ -496,8 +672,8 @@ static Class NSNumberK;
[NSException raise: @"MAPIStoreIOException"
format: @"no version record found for message '%@'",
messageKey];
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
inChangeListOnly: YES];
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
withOldChangeKey: oldChangeKey];
[versionsMessage save];
}
}
+29 -13
View File
@@ -180,7 +180,8 @@
{
uint64_t version = ULLONG_MAX;
NSString *changeNumber;
BOOL synced;
if (!isNew)
{
changeNumber = [(MAPIStoreGCSFolder *) container
@@ -189,16 +190,28 @@
{
[self warnWithFormat: @"attempting to get change number"
@" by synchronising folder..."];
[(MAPIStoreGCSFolder *) container synchroniseCache];
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
if (changeNumber)
[self logWithFormat: @"got one"];
else
synced = [(MAPIStoreGCSFolder *) container synchroniseCache];
if (synced)
{
[self errorWithFormat: @"still nothing. We crash!"];
abort();
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
}
if (!changeNumber)
{
[self warnWithFormat: @"attempting to get change number"
@" by synchronising this specific message..."];
synced = [(MAPIStoreGCSFolder *) container
synchroniseCacheFor: [self nameInContainer]];
if (synced)
{
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
}
if (!changeNumber)
{
[self errorWithFormat: @"still nothing. We crash!"];
abort();
}
}
}
version = [changeNumber unsignedLongLongValue] >> 16;
@@ -209,13 +222,16 @@
- (void) updateVersions
{
NSData *newChangeKey;
/* Update ChangeKey and PredecessorChangeList on message's save */
NSData *newChangeKey, *predecessorChangeList;
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
[(MAPIStoreGCSFolder *) container
updateVersionsForMessageWithKey: [self nameInContainer]
withChangeKey: newChangeKey];
updateVersionsForMessageWithKey: [self nameInContainer]
withChangeKey: newChangeKey
andPredecessorChangeList: predecessorChangeList];
}
@end
+4 -15
View File
@@ -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];
+14 -14
View File
@@ -67,7 +67,7 @@ MakeDisplayFolderName (NSString *folderName)
NSArray *parts;
NSString *lastFolder;
NSUInteger max;
parts = [folderName componentsSeparatedByString: @"/"];
max = [parts count];
if (max > 1)
@@ -160,23 +160,25 @@ MakeDisplayFolderName (NSString *folderName)
for (count = 0; count < max; count++)
{
context = talloc_zero (memCtx, struct mapistore_contexts_list);
// secondaryFolders has the names (1) Imap4Encoded and (2) asCSSIdentifier
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
// secondaryFolders has the names (1) Imap4Encoded ,(2) asCSSIdentifier and (3) "folder"-prefixed
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
currentName = [secondaryFolders objectAtIndex: count];
// To get the real name we have to revert that (applying the decode functions)
// in reverse order
// To get the real name we have to revert that (applying the decode functions
// in reverse order)
// e.g.: Problèmes de synchronisation
realName = [[currentName fromCSSIdentifier]
stringByDecodingImap4FolderName];
realName = [[[currentName substringFromIndex: 6]
fromCSSIdentifier]
stringByDecodingImap4FolderName];
// And finally to represent that as URI we have to (1) asCSSIdentifier,
// (2) Imap4Encode and (3) AddPercentEscapes
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
// (2) Imap4Encode (3) AddPercentEscapes and (4) add the "folder" prefix
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
// In the example there are no percent escapes added because is already ok
stringData = [[[realName asCSSIdentifier]
stringByEncodingImap4FolderName]
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
stringData = [NSString stringWithFormat: @"folder%@", stringData];
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context];
context->name = [[realName substringFromIndex: 6] asUnicodeInMemCtx: context];
context->name = [realName asUnicodeInMemCtx: context];
context->main_folder = false;
context->role = MAPISTORE_MAIL_ROLE;
context->tag = "tag";
@@ -198,10 +200,9 @@ MakeDisplayFolderName (NSString *folderName)
userContext = [MAPIStoreUserContext userContextWithUsername: userName
andTDBIndexing: NULL];
[MAPIApp setUserContext: userContext];
accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
folderName = [NSString stringWithFormat: @"folder%@",
[newFolderName asCSSIdentifier]];
[[newFolderName stringByEncodingImap4FolderName] asCSSIdentifier]];
newFolder = [SOGoMailFolder objectWithName: folderName
inContainer: accountFolder];
if ([newFolder create])
@@ -210,10 +211,9 @@ MakeDisplayFolderName (NSString *folderName)
withString: @"%40"],
[userName stringByReplacingOccurrencesOfString: @"@"
withString: @"%40"],
[[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
[folderName stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
else
mapistoreURI = nil;
[MAPIApp setUserContext: nil];
return mapistoreURI;
}
+2
View File
@@ -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;
+139 -58
View File
@@ -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
@@ -139,7 +142,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
[[self mapping] updateID: fid withURL: [self url]];
[dbFolder setNameInContainer: newNameInContainer];
[self cleanupCaches];
propsCopy = [newProperties mutableCopy];
[propsCopy removeObjectForKey: key];
[propsCopy autorelease];
@@ -186,10 +189,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
nameInContainer = [NSString stringWithFormat: @"folder%@",
[[folderName stringByEncodingImap4FolderName] asCSSIdentifier]];
/* it may be the operation is interleaved with operations
from other users having cached information in the thread
with the other user, so it'd better activate the user again here... */
[[self userContext] activateWithUser: [[[self userContext] woContext] activeUser]];
[[self userContext] activate];
newFolder = [SOGoMailFolderK objectWithName: nameInContainer
inContainer: sogoObject];
@@ -245,7 +245,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
sortOrdering: nil]
count];
*data = MAPILongValue (memCtx, longValue);
return MAPISTORE_SUCCESS;
}
@@ -253,7 +253,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
inMemCtx: (TALLOC_CTX *) memCtx
{
*data = [@"IPF.Note" asUnicodeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -265,7 +265,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
if (!nonDeletedQualifier)
{
deletedQualifier
= [[EOKeyValueQualifier alloc]
= [[EOKeyValueQualifier alloc]
initWithKey: @"FLAGS"
operatorSelector: EOQualifierOperatorContains
value: [NSArray arrayWithObject: @"Deleted"]];
@@ -378,7 +378,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
[self warnWithFormat: @"qualifier is only used for folders with name"];
if (sortOrderings)
[self errorWithFormat: @"sort orderings are not used for folders"];
/* FIXME: Flush any cache before retrieving the hierarchy, this
slows things down but it is safer */
if (!qualifier)
@@ -472,7 +472,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
}
else
supportsSubFolders = YES;
return supportsSubFolders;
}
@@ -519,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
{
@@ -529,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 */
@@ -556,8 +594,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
{
BOOL rc = YES;
uint64_t newChangeNum;
NSNumber *ti, *modseq, *initialLastModseq, *lastModseq,
*nextModseq;
NSNumber *ti, *modseq, *lastModseq, *nextModseq;
NSString *changeNumber, *uid, *messageKey;
uint64_t lastModseqNbr;
EOQualifier *searchQualifier;
@@ -596,7 +633,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
}
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
initialLastModseq = lastModseq;
if (lastModseq)
{
lastModseqNbr = [lastModseq unsignedLongLongValue];
@@ -641,7 +677,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
fetchResults
= [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
context: NULL];
for (count = 0; count < max; count++)
{
result = [fetchResults objectAtIndex: count];
@@ -680,40 +716,37 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
}
/* 2. we synchronise expunged UIDs */
if (initialLastModseq)
fetchResults = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: lastModseqNbr];
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
{
fetchResults = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: lastModseqNbr];
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
uid = [[fetchResults objectAtIndex: count] stringValue];
if ([messages objectForKey: uid])
{
uid = [[fetchResults objectAtIndex: count] stringValue];
if ([messages objectForKey: uid])
if (!changeNumber)
{
if (!changeNumber)
{
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
}
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
}
else
{
[self logWithFormat:@"Message entry not found for UID %@", uid];
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
}
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
}
if (changeNumber)
else
{
[currentProperties setObject: changeNumber
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
[self logWithFormat:@"Message entry not found for UID %@", uid];
}
}
if (changeNumber)
{
[currentProperties setObject: changeNumber
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
}
if (foundChange)
{
@@ -927,8 +960,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
return changeNumber;
}
- (void) setChangeKey: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
{
NSMutableDictionary *messages, *messageEntry;
NSString *messageUid;
@@ -939,7 +971,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
messageEntry = [messages objectForKey: messageUid];
if (!messageEntry)
{
[self warnWithFormat: @"attempting to synchronise to set the change key for "
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
@"this message %@", messageKey];
synced = [self synchroniseCacheForUID: messageUid];
if (synced)
@@ -950,11 +982,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
abort ();
}
}
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
return messageEntry;
}
- (void) setChangeKey: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey
{
[self _setChangeKey: changeKey
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
[versionsMessage save];
}
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
forMessageWithKey: (NSString *) messageKey
{
/* Update predecessor change list property given the change key. It
returns if the change key has been added to the list or not */
BOOL added = NO;
NSData *globCnt, *oldGlobCnt;
NSDictionary *messageEntry;
NSMutableDictionary *changeList;
NSString *guid;
struct XID *xid;
xid = [changeKey asXIDInMemCtx: NULL];
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
talloc_free (xid);
messageEntry = [self _messageEntryFromMessageKey: messageKey];
if (messageEntry)
{
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
if (changeList)
{
oldGlobCnt = [changeList objectForKey: guid];
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
{
[changeList setObject: globCnt forKey: guid];
[versionsMessage save];
added = YES;
}
}
else
[self errorWithFormat: @"Missing predecessor change list to update"];
}
return added;
}
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
{
NSDictionary *messages, *changeKeyDict;
@@ -1178,7 +1256,7 @@ _parseIMAPRange (const unichar *uniString, NSArray **UIDsP)
count++;
}
*UIDsP = UIDs;
return count;
}
@@ -1220,6 +1298,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
fromFolder: (MAPIStoreFolder *) sourceFolder
withMIDs: (uint64_t *) targetMids
andChangeKeys: (struct Binary_r **) targetChangeKeys
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
wantCopy: (uint8_t) wantCopy
inMemCtx: (TALLOC_CTX *) memCtx
@@ -1234,12 +1313,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
NSDictionary *result;
NSUInteger count;
NSArray *a;
NSData *changeKey;
NSData *changeList;
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
fromFolder: sourceFolder withMIDs: targetMids
andChangeKeys: targetChangeKeys
andPredecessorChangeLists: targetPredecessorChangeLists
wantCopy: wantCopy
inMemCtx: memCtx];
@@ -1328,11 +1408,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
[self synchroniseCache];
for (count = 0; count < midCount; count++)
{
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
messageKey = [NSString stringWithFormat: @"%@.eml",
[destUIDs objectAtIndex: count]];
[self setChangeKey: changeKey
forMessageWithKey: messageKey];
[self _updatePredecessorChangeListWith: changeList
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
}
}
@@ -1456,7 +1536,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
[uids addObject: [self messageUIDFromMessageKey: childKey]];
}
result = [client copyUids: uids
result = [client copyUids: uids
toFolder: newFolderIMAPName];
if ([[result objectForKey: @"result"] boolValue])
{
@@ -1495,9 +1575,10 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
{
SOGoCacheObject *childObject;
childObject = [SOGoCacheObject objectWithName: [SOGoCacheObject
globallyUniqueObjectId]
inContainer: sogoObject];
[[[self context] userContext] activate];
childObject = [SOGoCacheObject objectWithName: [SOGoCacheObject globallyUniqueObjectId]
inContainer: sogoObject];
return [MAPIStoreMailVolatileMessage
mapiStoreObjectWithSOGoObject: childObject
inContainer: self];
@@ -1573,7 +1654,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
rights |= RoleNone; /* actually "folder visible" */
// [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights];
return rights;
}
@@ -1624,7 +1705,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
}
}
}
client = [[(SOGoMailFolder *) sogoObject imap4Connection] client];
[client select: [sogoObject absoluteImap4Name]];
response = [client fetchUids: [keyAssoc allKeys]
+56 -28
View File
@@ -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";
@@ -1614,6 +1612,8 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
max = [keyParts count];
if (max > 0)
{
[[self userContext] activate];
currentPart = [sogoObject lookupName: [keyParts objectAtIndex: 0]
inContext: nil
acquire: NO];
@@ -1643,15 +1643,13 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
return attachment;
}
- (int) setReadFlag: (uint8_t) flag
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
NSString *imapFlag = @"\\Seen";
/* TODO: notifications should probably be emitted from here */
if (flag & CLEAR_READ_FLAG)
[sogoObject removeFlags: imapFlag];
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
else
[sogoObject addFlags: imapFlag];
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
return MAPISTORE_SUCCESS;
}
@@ -1692,9 +1690,12 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
return nil;
}
- (void) save: (TALLOC_CTX *) memCtx
- (void) save: (TALLOC_CTX *) memCtx
{
BOOL modified = NO;
BOOL seen, storedSeenFlag;
NSNumber *value;
NSString *imapFlag = @"\\Seen";
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
if (value)
@@ -1704,8 +1705,35 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
[sogoObject addFlags: @"\\Flagged"];
else /* 0: unflagged, 1: follow up complete */
[sogoObject removeFlags: @"\\Flagged"];
modified = YES;
}
/* Manage seen flag on save */
value = [properties objectForKey: @"read_flag_set"];
if (value)
{
seen = [value boolValue];
storedSeenFlag = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"] containsObject: @"seen"];
/* We modify the flags anyway to generate a new change number */
if (seen)
{
if (storedSeenFlag)
[sogoObject removeFlags: imapFlag];
[sogoObject addFlags: imapFlag];
}
else
{
if (!storedSeenFlag)
[sogoObject addFlags: imapFlag];
[sogoObject removeFlags: imapFlag];
}
modified = YES;
}
if (modified)
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
if (mailIsSharingObject)
[[self _sharingObject] saveWithMessage: self
andSOGoObject: sogoObject];
+22 -7
View File
@@ -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;
+36 -15
View File
@@ -34,6 +34,7 @@
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSObject+Values.h>
#import <NGExtensions/NSString+Encoding.h>
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeMultipartBody.h>
@@ -166,9 +167,9 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
- (BOOL) hasContentId
{
return ([properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
!= nil);
NSString *contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
return contentId && [contentId length] > 0;
}
- (NGMimeBodyPart *) asMIMEBodyPart
@@ -232,7 +233,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
[map addObject: contentDisposition forKey: @"content-disposition"];
contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
if (contentId)
if (contentId && [contentId length] > 0)
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
forKey: @"content-id"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
@@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
version = [properties objectForKey: @"version"];
return (version
? exchange_globcnt ([version unsignedLongLongValue])
? [version unsignedLongLongValue]
: ULLONG_MAX);
}
@@ -771,6 +772,12 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_NORMALIZED_SUBJECT_UNICODE)];
if (subjectData)
[subject appendString: subjectData];
if ([subject length] == 0)
{
subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_SUBJECT_UNICODE)];
if (subjectData)
[subject appendString: subjectData];
}
[headers setObject: [subject asQPSubjectString: @"utf-8"] forKey: @"subject"];
messageId = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_MESSAGE_ID_UNICODE)];
@@ -1077,6 +1084,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
dd = [activeUser domainDefaults];
from = [[activeUser allEmails] objectAtIndex: 0];
[[self userContext] activate];
woContext = [[self userContext] woContext];
authenticator = [sogoObject authenticatorInContext: woContext];
error = [[SOGoMailer mailerWithDomainDefaults: dd]
@@ -1103,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;
@@ -1139,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"];
}
}
+1 -1
View File
@@ -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;
+75 -29
View File
@@ -178,56 +178,101 @@ rtf2html (NSData *compressedRTF)
andColumns: (struct SPropTagArray *) columns
{
NSMutableDictionary *recipientProperties;
enum MAPITAGS prop_tag;
char *displayName = NULL;
char *email = NULL;
enum MAPI_OBJTYPE object_type = 0;
char *smtpAddress = NULL;
SOGoUser *recipientUser;
NSUInteger count;
NSString *recipientEmail = nil;
NSString *recipientFullName = nil;
id value;
recipientProperties = [NSMutableDictionary dictionaryWithCapacity: columns->cValues + 2];
for (count = 0; count < columns->cValues; count++)
{
prop_tag = columns->aulPropTag[count];
switch(prop_tag) {
case PidTagDisplayName:
displayName = recipient->data[count];
break;
case PidTagEmailAddress:
email = recipient->data[count];
break;
case PidTagObjectType:
if (recipient->data[count])
object_type = *((uint8_t*) recipient->data[count]);
break;
case PidTagSmtpAddress:
smtpAddress = recipient->data[count];
break;
default:
break;
}
if (recipient->data[count])
{
value = NSObjectFromValuePointer (prop_tag,
recipient->data[count]);
if (value)
[recipientProperties setObject: value
forKey: MAPIPropertyKey (prop_tag)];
}
}
if (recipient->username)
{
value = [NSString stringWithUTF8String: recipient->username];
[recipientProperties setObject: value forKey: @"x500dn"];
}
if (object_type == MAPI_MAILUSER && recipient->username)
{
/* values from user object have priority over data sent by the client */
recipientUser = [SOGoUser userWithLogin: [value lowercaseString]];
if (recipientUser)
{
value = [recipientUser cn];
if ([value length] > 0)
[recipientProperties setObject: value forKey: @"fullName"];
recipientFullName = value;
value = [[recipientUser allEmails] objectAtIndex: 0];
if ([value length] > 0)
[recipientProperties setObject: value forKey: @"email"];
}
}
else
{
if (recipient->data[0])
{
value = [NSString stringWithUTF8String: recipient->data[0]];
if ([value length] > 0)
[recipientProperties setObject: value forKey: @"fullName"];
}
if (recipient->data[1])
{
value = [NSString stringWithUTF8String: recipient->data[1]];
if ([value length] > 0)
[recipientProperties setObject: value forKey: @"email"];
}
recipientEmail = value;
}
}
for (count = 0; count < columns->cValues; count++)
/* If we do not have values from the user object we try to get them from the parameters */
if (!recipientFullName && displayName)
{
if (recipient->data[count])
{
value = NSObjectFromValuePointer (columns->aulPropTag[count],
recipient->data[count]);
if (value)
[recipientProperties setObject: value
forKey: MAPIPropertyKey (columns->aulPropTag[count])];
}
value = [NSString stringWithUTF8String: displayName];
if ([value length] > 0)
recipientFullName = value;
}
if (!recipientEmail && email)
{
value = [NSString stringWithUTF8String: email];
if ([value length] > 0)
recipientEmail = value;
}
if (!recipientEmail && smtpAddress)
{
value = [NSString stringWithUTF8String: smtpAddress];
if ([value length] > 0)
recipientEmail = value;
}
/* Now we can set the properties if we have them */
if (recipientFullName)
[recipientProperties setObject: recipientFullName forKey: @"fullName"];
if (recipientEmail)
[recipientProperties setObject: recipientEmail forKey: @"email"];
return recipientProperties;
}
@@ -504,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])
{
@@ -873,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];
+37 -117
View File
@@ -61,20 +61,25 @@ static BOOL initialization_done = NO;
if (!initialization_done) { \
OC_DEBUG(5, "[SOGo] You should call sogo_backend_init() first. Current thread: %p, pid: %d\n", \
GSCurrentThread(), getpid()); \
}
} \
OC_DEBUG(5, "[SOGo] --->");
#define NS_CURRENT_THREAD_TRY_UNREGISTER() \
if (__nsrct_thread_registered) { \
GSUnregisterCurrentThread(); \
}
} \
OC_DEBUG(6, "[SOGo] <---");
#define TRYCATCH_START @try {
#define TRYCATCH_END(pool) \
} @catch (NSException * e) { \
enum mapistore_error ret = sogo_backend_handle_objc_exception(e, __PRETTY_FUNCTION__, __LINE__); \
mapiapp_cleanup(); \
[pool release]; \
NS_CURRENT_THREAD_TRY_UNREGISTER(); \
return ret; \
}
} \
mapiapp_cleanup();
static enum mapistore_error
@@ -196,12 +201,28 @@ sogo_backend_init (void)
return MAPISTORE_SUCCESS;
}
/**
\details Cleanup operation to execute after an action has been performed
so there won't be any conflicts with future calls.
In practice this will deactivate the current user context set on MAPIApp
(which is the current WOApplication), this means two things: (1) set nil
as current user context on MAPIApp and (2) remove woContext from current
thread dictionary (this is used on WOContext.m).
*/
static void mapiapp_cleanup(void)
{
Class MAPIApplicationK;
MAPIApplicationK = NSClassFromString (@"MAPIApplication");
if (MAPIApplicationK)
[[MAPIApplicationK application] cleanup];
}
/**
\details Create a connection context to the sogo backend
\param mem_ctx pointer to the memory context
\param uri pointer to the sogo path
\param private_data pointer to the private backend context
\param private_data pointer to the private backend context
*/
static enum mapistore_error
@@ -214,8 +235,6 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx,
MAPIStoreContext *context;
int rc;
OC_DEBUG(5, "[SOGo]");
NS_CURRENT_THREAD_REGISTER();
pool = [NSAutoreleasePool new];
@@ -251,8 +270,6 @@ sogo_backend_create_root_folder (const char *username,
NSString *mapistoreUri;
int rc;
OC_DEBUG(5, "[SOGo]");
NS_CURRENT_THREAD_REGISTER();
pool = [NSAutoreleasePool new];
@@ -288,8 +305,6 @@ sogo_backend_list_contexts(const char *username, struct indexing_context *indexi
NSString *userName;
int rc;
OC_DEBUG(5, "[SOGo]");
NS_CURRENT_THREAD_REGISTER();
pool = [NSAutoreleasePool new];
@@ -336,8 +351,6 @@ sogo_context_get_path(void *backend_object, TALLOC_CTX *mem_ctx,
MAPIStoreContext *context;
int rc;
OC_DEBUG(5, "[SOGo]");
if (backend_object)
{
wrapper = backend_object;
@@ -370,8 +383,6 @@ sogo_context_get_root_folder(void *backend_object, TALLOC_CTX *mem_ctx,
MAPIStoreFolder *folder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (backend_object)
{
wrapper = backend_object;
@@ -413,8 +424,6 @@ sogo_folder_open_folder(void *folder_object, TALLOC_CTX *mem_ctx, uint64_t fid,
MAPIStoreFolder *folder, *childFolder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -442,7 +451,7 @@ sogo_folder_open_folder(void *folder_object, TALLOC_CTX *mem_ctx, uint64_t fid,
/**
\details Create a folder in the sogo backend
\param private_data pointer to the current sogo context
\return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR
@@ -457,8 +466,6 @@ sogo_folder_create_folder(void *folder_object, TALLOC_CTX *mem_ctx,
MAPIStoreFolder *folder, *childFolder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -500,8 +507,6 @@ sogo_folder_delete(void *folder_object)
MAPIStoreFolder *folder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -532,8 +537,6 @@ sogo_folder_get_child_count(void *folder_object, enum mapistore_table_type table
MAPIStoreFolder *folder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -568,8 +571,6 @@ sogo_folder_open_message(void *folder_object,
MAPIStoreMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -610,8 +611,6 @@ sogo_folder_create_message(void *folder_object,
MAPIStoreMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -622,7 +621,7 @@ sogo_folder_create_message(void *folder_object,
TRYCATCH_START
rc = [folder createMessage: &message
withMID: mid
isAssociated: associated];
isAssociated: associated];
if (rc == MAPISTORE_SUCCESS)
*message_object = [message tallocWrapper: mem_ctx];
TRYCATCH_END(pool)
@@ -646,8 +645,6 @@ sogo_folder_delete_message(void *folder_object, uint64_t mid, uint8_t flags)
MAPIStoreFolder *folder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -677,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;
@@ -684,8 +682,6 @@ sogo_folder_move_copy_messages(void *folder_object,
struct MAPIStoreTallocWrapper *wrapper;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -703,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)
@@ -728,8 +725,6 @@ sogo_folder_move_folder(void *folder_object, void *target_folder_object,
struct MAPIStoreTallocWrapper *wrapper;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -778,8 +773,6 @@ sogo_folder_copy_folder(void *folder_object, void *target_folder_object, TALLOC_
struct MAPIStoreTallocWrapper *wrapper;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -822,8 +815,6 @@ sogo_folder_get_deleted_fmids(void *folder_object, TALLOC_CTX *mem_ctx,
MAPIStoreFolder *folder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -861,8 +852,6 @@ sogo_folder_open_table(void *folder_object, TALLOC_CTX *mem_ctx,
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -900,8 +889,6 @@ sogo_folder_modify_permissions(void *folder_object, uint8_t flags,
MAPIStoreFolder *folder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -934,8 +921,6 @@ sogo_folder_preload_message_bodies(void *folder_object, enum mapistore_table_typ
MAPIStoreFolder *folder;
int rc;
OC_DEBUG(5, "[SOGo]");
if (folder_object)
{
wrapper = folder_object;
@@ -969,8 +954,6 @@ sogo_message_get_message_data(void *message_object,
MAPIStoreMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (message_object)
{
wrapper = message_object;
@@ -1004,8 +987,6 @@ sogo_message_create_attachment (void *message_object, TALLOC_CTX *mem_ctx, void
MAPIStoreAttachment *attachment;
int rc;
OC_DEBUG(5, "[SOGo]");
if (message_object)
{
wrapper = message_object;
@@ -1017,7 +998,6 @@ sogo_message_create_attachment (void *message_object, TALLOC_CTX *mem_ctx, void
rc = [message createAttachment: &attachment inAID: aidp];
if (rc == MAPISTORE_SUCCESS)
*attachment_object = [attachment tallocWrapper: mem_ctx];
// [context tearDownRequest];
TRYCATCH_END(pool)
[pool release];
@@ -1041,8 +1021,6 @@ sogo_message_open_attachment (void *message_object, TALLOC_CTX *mem_ctx,
MAPIStoreAttachment *attachment;
int rc;
OC_DEBUG(5, "[SOGo]");
if (message_object)
{
wrapper = message_object;
@@ -1054,7 +1032,6 @@ sogo_message_open_attachment (void *message_object, TALLOC_CTX *mem_ctx,
rc = [message getAttachment: &attachment withAID: aid];
if (rc == MAPISTORE_SUCCESS)
*attachment_object = [attachment tallocWrapper: mem_ctx];
// [context tearDownRequest];
TRYCATCH_END(pool)
[pool release];
@@ -1077,8 +1054,6 @@ sogo_message_get_attachment_table (void *message_object, TALLOC_CTX *mem_ctx, vo
MAPIStoreAttachmentTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (message_object)
{
wrapper = message_object;
@@ -1091,7 +1066,6 @@ sogo_message_get_attachment_table (void *message_object, TALLOC_CTX *mem_ctx, vo
andRowCount: row_count];
if (rc == MAPISTORE_SUCCESS)
*table_object = [table tallocWrapper: mem_ctx];
// [context tearDownRequest];
TRYCATCH_END(pool)
[pool release];
@@ -1116,9 +1090,6 @@ sogo_message_modify_recipients (void *message_object,
MAPIStoreMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (message_object)
{
wrapper = message_object;
@@ -1130,7 +1101,6 @@ sogo_message_modify_recipients (void *message_object,
rc = [message modifyRecipientsWithRecipients: recipients
andCount: count
andColumns: columns];
// [context tearDownRequest];
TRYCATCH_END(pool)
[pool release];
@@ -1150,10 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
struct MAPIStoreTallocWrapper *wrapper;
NSAutoreleasePool *pool;
MAPIStoreMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
enum mapistore_error rc;
if (message_object)
{
@@ -1164,7 +1131,6 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
TRYCATCH_START
rc = [message setReadFlag: flag];
// [context tearDownRequest];
TRYCATCH_END(pool)
[pool release];
@@ -1186,9 +1152,6 @@ sogo_message_save (void *message_object, TALLOC_CTX *mem_ctx)
MAPIStoreMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (message_object)
{
wrapper = message_object;
@@ -1198,7 +1161,6 @@ sogo_message_save (void *message_object, TALLOC_CTX *mem_ctx)
TRYCATCH_START
rc = [message saveMessage: mem_ctx];
// [context tearDownRequest];
TRYCATCH_END(pool)
[pool release];
@@ -1220,9 +1182,6 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags)
MAPIStoreMailVolatileMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (message_object)
{
wrapper = message_object;
@@ -1232,7 +1191,6 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags)
TRYCATCH_START
rc = [message submitWithFlags: flags];
// [context tearDownRequest];
TRYCATCH_END(pool)
[pool release];
@@ -1259,9 +1217,6 @@ sogo_message_attachment_open_embedded_message (void *attachment_object,
MAPIStoreEmbeddedMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (attachment_object)
{
wrapper = attachment_object;
@@ -1301,9 +1256,6 @@ sogo_message_attachment_create_embedded_message (void *attachment_object,
MAPIStoreEmbeddedMessage *message;
int rc;
OC_DEBUG(5, "[SOGo]");
if (attachment_object)
{
wrapper = attachment_object;
@@ -1338,9 +1290,6 @@ static enum mapistore_error sogo_table_get_available_properties(void *table_obje
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (table_object)
{
wrapper = table_object;
@@ -1371,9 +1320,6 @@ sogo_table_set_columns (void *table_object, uint16_t count, enum MAPITAGS *prope
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (table_object)
{
wrapper = table_object;
@@ -1405,9 +1351,6 @@ sogo_table_set_restrictions (void *table_object, struct mapi_SRestriction *restr
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (table_object)
{
wrapper = table_object;
@@ -1441,9 +1384,6 @@ sogo_table_set_sort_order (void *table_object, struct SSortOrderSet *sort_order,
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (table_object)
{
wrapper = table_object;
@@ -1479,9 +1419,6 @@ sogo_table_get_row (void *table_object, TALLOC_CTX *mem_ctx,
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (table_object)
{
wrapper = table_object;
@@ -1515,9 +1452,6 @@ sogo_table_get_row_count (void *table_object,
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (table_object)
{
wrapper = table_object;
@@ -1549,9 +1483,6 @@ sogo_table_handle_destructor (void *table_object, uint32_t handle_id)
MAPIStoreTable *table;
int rc;
OC_DEBUG(5, "[SOGo]");
if (table_object)
{
wrapper = table_object;
@@ -1584,9 +1515,6 @@ static enum mapistore_error sogo_properties_get_available_properties(void *objec
MAPIStoreObject *propObject;
int rc;
OC_DEBUG(5, "[SOGo]");
if (object)
{
wrapper = object;
@@ -1620,9 +1548,6 @@ sogo_properties_get_properties (void *object,
MAPIStoreObject *propObject;
int rc;
OC_DEBUG(5, "[SOGo]");
if (object)
{
wrapper = object;
@@ -1655,9 +1580,6 @@ sogo_properties_set_properties (void *object, struct SRow *aRow)
MAPIStoreObject *propObject;
int rc;
OC_DEBUG(5, "[SOGo]");
if (object)
{
wrapper = object;
@@ -1681,18 +1603,16 @@ sogo_properties_set_properties (void *object, struct SRow *aRow)
}
static enum mapistore_error
sogo_manager_generate_uri (TALLOC_CTX *mem_ctx,
const char *user,
const char *folder,
const char *message,
sogo_manager_generate_uri (TALLOC_CTX *mem_ctx,
const char *user,
const char *folder,
const char *message,
const char *rootURI,
char **uri)
{
NSAutoreleasePool *pool;
NSString *partialURLString, *username, *directory;
OC_DEBUG(5, "[SOGo]");
if (uri == NULL) return MAPISTORE_ERR_INVALID_PARAMETER;
/* This fixes a crash occurring during the instantiation of the
@@ -1738,9 +1658,9 @@ sogo_manager_generate_uri (TALLOC_CTX *mem_ctx,
*/
int mapistore_init_backend(void)
{
struct mapistore_backend backend;
int ret;
static BOOL registered = NO;
struct mapistore_backend backend;
int ret;
static BOOL registered = NO;
if (registered)
ret = MAPISTORE_SUCCESS;
+4 -2
View File
@@ -31,6 +31,7 @@
#import <Appointments/SOGoTaskObject.h>
#import "MAPIApplication.h"
#import "MAPIStoreUserContext.h"
#import "MAPIStoreTasksContext.h"
#import "MAPIStoreTasksMessage.h"
#import "MAPIStoreTasksMessageTable.h"
@@ -61,6 +62,7 @@
SOGoTaskObject *newEntry;
NSString *name;
[[[self context] userContext] activate];
name = [NSString stringWithFormat: @"%@.ics",
[SOGoObject globallyUniqueObjectId]];
newEntry = [SOGoTaskObject objectWithName: name
@@ -69,7 +71,7 @@
newMessage = [MAPIStoreTasksMessage mapiStoreObjectWithSOGoObject: newEntry
inContainer: self];
return newMessage;
}
@@ -116,7 +118,7 @@
rights |= RightsReadItems;
if (rights != 0)
rights |= RoleNone; /* actually "folder visible" */
return rights;
}
+1 -1
View File
@@ -531,7 +531,7 @@
}
[vToDo setTimeStampAsDate: now];
[sogoObject saveComponent: vCalendar];
[sogoObject saveCalendar: vCalendar];
[self updateVersions];
}
+2
View File
@@ -86,6 +86,8 @@
/* SOGo hacky magic */
- (void) activateWithUser: (SOGoUser *) activeUser;
- (void) activate;
- (void) deactivate;
- (MAPIStoreAuthenticator *) authenticator;
- (WOContext *) woContext;
+35 -7
View File
@@ -39,6 +39,7 @@
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/NSString+Utilities.h>
#import <NGExtensions/NSObject+Logs.h>
#import <Mailer/SOGoMailAccount.h>
#import <Mailer/SOGoMailAccounts.h>
@@ -158,6 +159,8 @@ static NSMapTable *contextsTable = nil;
if ([userPassword length] == 0)
userPassword = username;
[authenticator setPassword: userPassword];
// Activate the profile on initialization
[self activate];
}
return self;
@@ -214,7 +217,7 @@ static NSMapTable *contextsTable = nil;
if (!userFolder)
{
userFolder = [SOGoUserFolder objectWithName: username
inContainer: MAPIApp];
inContainer: nil];
[userFolder retain];
}
@@ -232,7 +235,8 @@ static NSMapTable *contextsTable = nil;
if (!rootFolders)
{
rootFolders = [NSMutableDictionary new];
[self userFolder];
[self activate];
[self userFolder]; // force lazy initialization
[woContext setClientObject: userFolder];
/* Calendar */
@@ -257,10 +261,10 @@ static NSMapTable *contextsTable = nil;
acquire: NO];
[containersBag addObject: accountsFolder];
[woContext setClientObject: accountsFolder];
currentFolder = [accountsFolder lookupName: @"0"
inContext: woContext
acquire: NO];
[rootFolders setObject: currentFolder
forKey: @"mail"];
connection = [currentFolder imap4Connection];
@@ -268,8 +272,7 @@ static NSMapTable *contextsTable = nil;
/* ensure the folder cache is filled */
[currentFolder toManyRelationshipKeysWithNamespaces: YES];
hierarchy = [connection
cachedHierarchyResultsForURL: [currentFolder imap4URL]];
hierarchy = [connection cachedHierarchyResultsForURL: [currentFolder imap4URL]];
flags = [[hierarchy objectForKey: @"list"] objectForKey: @"/INBOX"];
inboxHasNoInferiors = [flags containsObject: @"noinferiors"];
}
@@ -338,7 +341,7 @@ static NSMapTable *contextsTable = nil;
cm = [GCSChannelManager defaultChannelManager];
channel = [cm acquireOpenChannelForURL: folderTableURL];
/* FIXME: make use of [EOChannelAdaptor describeTableNames] instead */
tableName = [[folderTableURL path] lastPathComponent];
if ([channel evaluateExpressionX:
@@ -355,7 +358,7 @@ static NSMapTable *contextsTable = nil;
[channel cancelFetch];
[cm releaseChannel: channel];
[cm releaseChannel: channel];
}
/* SOGo context objects */
@@ -369,6 +372,11 @@ static NSMapTable *contextsTable = nil;
return authenticator;
}
- (void) activate
{
[self activateWithUser: [self sogoUser]];
}
- (void) activateWithUser: (SOGoUser *) activeUser;
{
NSMutableDictionary *info;
@@ -379,4 +387,24 @@ static NSMapTable *contextsTable = nil;
[info setObject: woContext forKey: @"WOContext"];
}
- (void) deactivate
{
NSMutableDictionary *info;
if (self == [MAPIApp userContext])
[MAPIApp setUserContext: nil];
else
[self errorWithFormat: @"Error: Tried to deactivate an user context "
@"not enabled (%@ vs %@)",
[self username], [[MAPIApp userContext] username]];
info = [[NSThread currentThread] threadDictionary];
if (woContext == [info objectForKey: @"WOContext"])
[info removeObjectForKey: @"WOContext"];
else
[self errorWithFormat: @"Error: Tried to deactivate a WOContext "
@"not enabled (%@ vs %@)",
woContext, [info objectForKey: @"WOContext"]];
}
@end
+2
View File
@@ -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;
+40 -6
View File
@@ -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 -1
View File
@@ -500,7 +500,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60;
ms = [NSMutableString stringWithCapacity: 256];
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
[ms appendFormat: @" #adaptors=%d", [urlToAdaptor count]];
[ms appendFormat: @" #adaptors=%d", (int)[urlToAdaptor count]];
[ms appendString: @">"];
return ms;
+1 -1
View File
@@ -414,7 +414,7 @@
if (group)
[str appendFormat: @"%@ (group: %@)\n", tag, group];
else
[str appendFormat: @"%@\n", tag, group];
[str appendFormat: @"%@\n", tag];
[str appendString: [self versitString]];
+1 -1
View File
@@ -409,7 +409,7 @@ static NGCardsSaxHandler *sax = nil;
max = [children count];
if (max > 0)
{
[str appendFormat: @"\n %d children: {\n", [children count]];
[str appendFormat: @"\n %d children: {\n", (int)[children count]];
for (count = 0; count < max; count++)
[str appendFormat: @" %@\n",
[[children objectAtIndex: count] description]];
+5 -5
View File
@@ -32,16 +32,16 @@
- (NSString *) iCalFormattedDateTimeString
{
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear],
[self dayOfMonth], [self hourOfDay],
[self minuteOfHour], [self secondOfMinute]];
(int)[self yearOfCommonEra], (int)[self monthOfYear],
(int)[self dayOfMonth], (int)[self hourOfDay],
(int)[self minuteOfHour], (int)[self secondOfMinute]];
}
- (NSString *) iCalFormattedDateString
{
return [NSString stringWithFormat: @"%.4d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear],
[self dayOfMonth]];
(int)[self yearOfCommonEra], (int)[self monthOfYear],
(int)[self dayOfMonth]];
}
@end
+15
View File
@@ -158,16 +158,31 @@
NSMutableString *string;
unsigned int len, i;
unichar c;
BOOL isQuoted;
len = [self length];
string = [NSMutableString stringWithCapacity: len * 1.5];
isQuoted = NO;
for (i = 0; i < len; i++)
{
c = [self characterAtIndex: i];
if (isQuoted)
{
if (c == '"')
isQuoted = NO;
[string appendFormat: @"%C", c];
continue;
}
switch (c)
{
case '"':
isQuoted = YES;
[string appendFormat: @"%C", c];
break;
case '\\':
[string appendString: @"\\\\"];
break;
+5 -1
View File
@@ -27,6 +27,7 @@
#import "NSCalendarDate+NGCards.h"
#import "iCalAlarm.h"
#import "iCalCalendar.h"
#import "iCalDateTime.h"
#import "iCalEntityObject.h"
#import "iCalEvent.h"
@@ -268,9 +269,12 @@
- (void) setRecurrenceId: (NSCalendarDate *) newRecId
{
iCalDateTime* recurrenceId;
BOOL isMasterAllDay;
isMasterAllDay = [[[[self parent] events] objectAtIndex: 0] isAllDay];
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay])
if ([self isKindOfClass: [iCalEvent class]] && isMasterAllDay)
[recurrenceId setDate: newRecId];
else
[recurrenceId setDateTime: newRecId];
@@ -348,7 +348,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]])
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay+1] = YES;
currentPos++;
}
@@ -362,7 +362,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]])
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay] = YES;
currentPos--;
}
+1 -1
View File
@@ -256,7 +256,7 @@
- (BOOL)isEqual:(id)_other {
if(_other == nil)
return NO;
if([_other class] != self->isa)
if([_other class] != object_getClass(self))
return NO;
if([_other hash] != [self hash])
return NO;
+2 -2
View File
@@ -309,10 +309,10 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
else if ([frequency isEqualToString:@"SECONDLY"])
freq = iCalRecurrenceFrequenceSecondly;
else
freq = NSNotFound;
freq = (iCalRecurrenceFrequency)NSNotFound;
}
else
freq = NSNotFound;
freq = (iCalRecurrenceFrequency)NSNotFound;
return freq;
}
+36 -4
View File
@@ -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);
@@ -1045,7 +1045,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn)
{
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...",
__PRETTY_FUNCTION__, _data, [_data length]);
__PRETTY_FUNCTION__, _data, (int)[_data length]);
}
if ((len = [_data length]) == 0)
@@ -1164,7 +1164,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn)
{
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
__PRETTY_FUNCTION__, _source, [_source length]);
__PRETTY_FUNCTION__, _source, (int)[_source length]);
}
if (!_sysId) _sysId = @"<string>";
[self _parseString: _source];
-309
View File
@@ -1,309 +0,0 @@
#!/usr/bin/env python
import getopt
import imaplib
import ldb
import plistlib
import os
import re
import shutil
import subprocess
import sys
from samba.param import LoadParm
imaphost = '127.0.0.1'
imapport = 143
samba_lp = LoadParm()
sambaprivate = samba_lp.get("private dir")
mapistorefolder = samba_lp.private_path("mapistore")
sogoSysDefaultsFile = "/etc/sogo/sogo.conf"
sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaults")
# - takes a username and optionally its password
# - removes the entry in samba's ldap tree via ldbedit (NOTYET)
# - remove the user's directory under mapistore/ and mapistore/SOGo
# - cleanup Junk Folders and Sync Issues imap folders
# - Delete the sogo_cache_folder_ table for the username.
def usage():
print """
%s [-i imaphost] ] [-p imapport] [-s sambaprivate] username [password]
-i imaphost IMAP host to connect to [%s]
-p imappost IMAP port to use [%d]
-s sambaprivate samba private directory [%s]
""" % (os.path.basename(sys.argv[0]), imaphost, imapport, sambaprivate)
def main():
global sambaprivate
global mapistorefolder
global imaphost
global imapport
try:
opts, args = getopt.getopt(sys.argv[1:], "i:p:s:")
except getopt.GetoptError, err:
print str(err)
usage()
sys.exit(2)
for o, a in opts:
if o == "-i":
imaphost = a
elif o == "-p":
imapport = a
elif o == "-s":
sambaprivate = a
mapistorefolder = "%s/mapistore" % (sambaprivate)
else:
assert False, "unhandled option"
argslen = len(args)
if (argslen == 2):
username = args[0]
userpass = args[1]
elif (argslen == 1):
username = args[0]
userpass = username
print "Using username as password"
else:
usage()
print "Specify a user (and optionally the password)"
sys.exit(2)
# cleanup starts here
try:
imapCleanup(imaphost, imapport, username, userpass)
except Exception as e:
print "Error during imapCleanup, continuing: %s" % str(e)
try:
mapistoreCleanup(mapistorefolder, username)
except (shutil.Error, OSError) as e:
print "Error during mapistoreCleanup, continuing: %s" % str(e)
try:
ldbCleanup(sambaprivate, username)
except ldb.LdbError as e:
print "Error during ldbCleanup, continuing: %s" % str(e)
try:
sqlCleanup(username)
except Exception as e:
print "Error during sqlCleanup, continuing: %s" % str(e)
def getsep(client):
seq = None
(code, data) = client.list("", "")
if code == "OK" and data is not None:
# yes this is ugly but it works on cyrus and dovecot.
# (\\Noinferiors \\HasNoChildren) "/" INBOX
m = re.search(".*\s+[\"\']?(.)[\"\']?\s+[\"\']?.*[\"\']?$", data[0])
sep = m.group(1)
return sep
def extractmb(si):
inparen = False
inquote = False
part = []
parts = []
for char in si:
if inparen:
if char == ")":
inparen = False
parts.append("".join(part))
else:
part.append(char)
elif inquote:
if char == "\"":
inquote = False
parts.append("".join(part))
else:
part.append(char)
else:
if char == "(":
inparen = True
elif char == "\"":
inquote = True
elif char == " ":
part = []
else:
part.append(char)
return parts[-1]
def cleanupmb(mb, sep, client):
(code, data) = client.list("%s%s" % (mb, sep), "%")
if code == "OK":
for si in data:
if si is not None:
submb = extractmb(si)
cleanupmb(submb, sep, client)
else:
raise Exception, "Failure while cleaning up '%s'" % mb
client.unsubscribe(mb)
(code, data) = client.delete(mb)
if code == "OK":
print "mailbox '%s' deleted" % mb
else:
print "mailbox '%s' coult NOT be deleted (code = '%s')" % (mb, code)
def imapCleanup(imaphost, imapport, username, userpass):
print "Starting IMAP cleanup"
client = imaplib.IMAP4(imaphost, imapport)
(code, data) = client.login(username, userpass)
if code != "OK":
raise Exception, "Login failure"
print "Logged in as '%s'" % username
sep = getsep(client)
if not sep:
client.logout()
return
for foldername in ("Sync Issues", "Junk E-mail",
"INBOX%sSync Issues" % sep, "INBOX%sJunk E-mail" % sep,
"Probl&AOg-mes de synchronisation"):
(code, data) = client.list(foldername, "%")
if code == "OK":
for si in data:
if si is not None:
mb = extractmb(si)
cleanupmb(mb, sep, client)
client.logout()
def mapistoreCleanup(mapistorefolder, username):
print "Starting MAPIstore cleanup"
# delete the user's folder under the mapistore and under mapistore/SOGo
mapistoreUserDir = "%s/%s" % (mapistorefolder, username)
for dirpath, dirnames, filenames in os.walk(mapistoreUserDir):
for f in filenames:
if f != "password":
os.unlink("%s/%s" % (dirpath,f))
break #one level only
shutil.rmtree("%s/SOGo/%s" % (mapistorefolder, username), ignore_errors=True)
def ldbCleanup(sambaprivate, username):
conn = ldb.Ldb("%s/openchange.ldb" % (sambaprivate))
# find the DN of the user
entries = conn.search(None, expression="(cn=%s)" % (username), scope=ldb.SCOPE_SUBTREE)
if not entries:
print "cn = %s not found in openchange.ldb" %(username)
return
for entry in entries:
# search again, but with the user's DN as a base
subentries = conn.search(entry.dn.extended_str(), expression="(distinguishedName=*)", scope=ldb.SCOPE_SUBTREE)
for subentry in subentries:
print "Deleting %s" % (subentry.dn)
conn.delete(subentry.dn)
def mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
try:
import MySQLdb
except ImportError:
msg ="""The python 'MySQLdb' module is not available
On Debian based distro, install it using 'apt-get install python-mysqlbd'
On RHEL, install it using 'yum install MySQL-python'"""
raise Exception(msg)
conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
c=conn.cursor()
tablename="sogo_cache_folder_%s" % (username)
c.execute("TRUNCATE TABLE %s" % tablename)
print "Table %s emptied" % tablename
def postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
try:
import pg
except ImportError:
msg ="""The python 'pg' module is not available
On Debian based distro, install it using 'apt-get install python-pygresql'
On RHEL, install it using 'yum install python-pgsql'"""
raise Exception(msg)
conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname)
tablename = "sogo_cache_folder_%s" % username
conn.query("DELETE FROM %s" % tablename)
print "Table '%s' emptied" % tablename
def getOCSFolderInfoURL():
global sogoSysDefaultsFile, sogoUserDefaultsFile
OCSFolderInfoURL = ""
# read defaults from defaults files
# order is important, user defaults must have precedence
for f in [sogoSysDefaultsFile, sogoUserDefaultsFile]:
if os.path.exists(f):
# FIXME: this is ugly, we should have a python plist parser
# plistlib only supports XML plists.
# the following magic replaces this shell pipeline:
# sogo-tool dump-defaults -f %s | awk -F\\" '/ OCSFolderInfoURL =/ {print $2}'
p1 = subprocess.Popen(["sogo-tool", "dump-defaults", "-f", f], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["awk", "-F\"", "/ OCSFolderInfoURL =/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
tmp = p2.communicate()[0]
if tmp: OCSFolderInfoURL = tmp
return OCSFolderInfoURL
def asCSSIdentifier(inputString):
cssEscapingCharMap = {"_" : "_U_",
"." : "_D_",
"#" : "_H_",
"@" : "_A_",
"*" : "_S_",
":" : "_C_",
"," : "_CO_",
" " : "_SP_",
"'" : "_SQ_",
"&" : "_AM_",
"+" : "_P_"}
newChars = []
for c in inputString:
if c in cssEscapingCharMap:
newChars.append(cssEscapingCharMap[c])
else:
newChars.append(c)
return "".join(newChars)
def sqlCleanup(username):
print "Starting SQL cleanup"
OCSFolderInfoURL = getOCSFolderInfoURL()
if OCSFolderInfoURL is None:
raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username))
# postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info
m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL)
if not m:
raise Exception("Unable to parse OCSFolderInfoURL: %s" % OCSFolderInfoURL)
proto = m.group(1)
dbuser = m.group(2)
dbpass = m.group(3)
dbhost = m.group(4)
dbport = m.group(5)
dbname = m.group(6)
# 7 is folderinfo table
encodedUserName = asCSSIdentifier(username)
if (proto == "postgresql"):
postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
elif (proto == "mysql"):
mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
else:
raise Exception("Unknown sql proto: %s" % (proto))
if __name__ == "__main__":
main()
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python
import sys
import os
import re
DEBUG=False
dir_mappings = {"../UI/Templates":"../UI/Common",
"../UI/Templates/AdministrationUI":"../UI/AdministrationUI",
"../UI/Templates/Appointments":"../SoObjects/Appointments",
"../UI/Templates/ContactsUI":"../UI/Contacts",
"../UI/Templates/MailerUI":"../UI/MailerUI",
"../UI/Templates/MailPartViewers":"../UI/MailPartViewers",
"../UI/Templates/MainUI":"../UI/MainUI",
"../UI/Templates/PreferencesUI":"../UI/PreferencesUI",
"../UI/Templates/SchedulerUI":"../UI/Scheduler"
}
def get_translations(path):
try:
transpath = dir_mappings.get(path, path)
transname = transpath + '/English.lproj/Localizable.strings'
transall = open(transname).read()
except:
transall = ""
return transall
def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=()):
for path, dirs, files in os.walk(rootdir):
filelist = [f for f in files if f[(-1 * len(extention)):] == extention]
if filelist:
for filename in filelist:
if filename in greylist:
print "%s is greylisted -- SKIPPING" % filename
continue
pathname = path + '/' + filename
lines = open(pathname).read().split("\n")
values = [r.groups()[0] for r in [recomp.search(l) for l in lines] if r]
if values:
#- Get the current english translations for the path
transgood = get_translations(path)
if not transgood:
print "No translation file found for %s, skipping %s" % (path, pathname)
continue
notfound = list()
if DEBUG:print pathname
for value in values:
#- Try to find the value from the source file in the translation file
escaped_value = re.escape(value)
found = re.search('(%s|"%s")\s*=\s*"(.*?)";' % (escaped_value, escaped_value), transgood)
if found:
if DEBUG: print "\t", '[%s] FOUND -- "%s"' % found.groups()
else:
notfound.append("-->\t[%s] ==== Not Found ====" % value)
if notfound:
if not DEBUG:print pathname
print "\n".join(notfound)
def main():
#- Only one option...
if len(sys.argv) > 1:
if sys.argv[1] == '-g':
global DEBUG
DEBUG = True
else:
print 'Usage:', sys.argv[0], '[-g]\n\t\t-g: debug will show matching also'
sys.exit(1)
greylist = ('UIxFilterEditor.wox')
#- Get only the label:value from all lines
recomp = re.compile(' label:[^=]*="(.*?)"')
find_missing_translations('../UI', 'wox', recomp, greylist)
#- [self labelForKey: @"Issuer"]
recomp = re.compile('\[self labelForKey: @"(.*?)"\]')
find_missing_translations('../UI', 'm', recomp, ())
main()
@@ -6,37 +6,32 @@ vevent_class2 = "(حدث سري)";
vtodo_class0 = "(مهمة عامة)";
vtodo_class1 = "(مهمة خاصة)";
vtodo_class2 = "(مهمة سرية)";
/* Receipts */
"The event \"%{Summary}\" was created" = "أُنشِئ الحدث \"%{Summary}\"";
"The event \"%{Summary}\" was deleted" = "حُذِفَ الحدث \"%{Summary}\"";
"The event \"%{Summary}\" was updated" = "حُدِّث الحدث \"%{Summary}\"";
"The following attendees(s) were notified:" = "تم ابلاغ المدعو (المدعويين) الأتي أسماءهم:";
"The following attendees(s) were added:" = "تم أضافة المدعو (المدعويين) الأتي أسماءهم:";
"The following attendees(s) were removed:" = "تم مسح المدعو (المدعويين) الأتي أسماءهم:";
"The following attendees(s) were notified" = "تم ابلاغ المدعو (المدعويين) الأتي أسماءهم";
"The following attendees(s) were added" = "تم أضافة المدعو (المدعويين) الأتي أسماءهم";
"The following attendees(s) were removed" = "تم مسح المدعو (المدعويين) الأتي أسماءهم";
/* IMIP messages */
"calendar_label" = "التقويم:";
"startDate_label" = "البداية:";
"endDate_label" = "النهاية:";
"calendar_label" = "التقويم";
"startDate_label" = "البداية";
"endDate_label" = "النهاية";
"due_label" = "تاريخ الاستحقاق:";
"location_label" = "المكان:";
"location_label" = "المكان";
"summary_label" = "الملخص:";
"comment_label" = "التعليق:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "دعوة الحدث: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(أرسلت بواسطة٪ {SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "وقد دعت%{Organizer} %{SentByText} الى %{Summary} . ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate}⏎\nالوصف:%{Description}\n";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}قام بدعوتك %{Summary}. ⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}\n";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "الحدث الغي : \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "ألغى%{Organizer} %{SentByText} هذا الحدث:.%{Summary} ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate} ⏎\nالوصف:%{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}قام بإلغاء هذا الحدث: %{Summary}.⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "موعد \"%{Summary}\" بتاريخ %{OldStartDate} قد تغير";
@@ -46,7 +41,6 @@ vtodo_class2 = "(مهمة سرية)";
= " لقد تغيرت المحددات التالية في الاجتماع \"%{Summary}\":";
"Please accept or decline those changes."
= "يرجى قبول أو رفض تلك التغييرات.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "دعوةٌ مقبولة: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "دعوةٌ مرفوضة: \"%{Summary}\"";
@@ -60,7 +54,6 @@ vtodo_class2 = "(مهمة سرية)";
= "وقد فوض%{Attendee} %{SentByText} الدعوة إلى %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} لم يقرر حتى الآن حضورهذا الحدث الخاص بك.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "لا يمكن الوصول إلى الموارد: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "الحد الأقصى لعدد الحجوزات في وقت واحد (%{NumberOfSimultaneousBookings}) وصلت للموارد \"%{Cn} %{SystemEmail}\" . الحدث المتضارب في الموعد هو \"٪ {EventTitle}\"، ويبدأ فى٪ {StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Isilpeko ekitaldia)";
vtodo_class0 = "(Zeregin publikoa)";
vtodo_class1 = "(Zeregin pribatua)";
vtodo_class2 = "(Isilpeko egitekoa)";
/* Receipts */
"The event \"%{Summary}\" was created" = "\"%{Summary}\" ekitaldia sortu da";
"The event \"%{Summary}\" was deleted" = "\"%{Summary}\" ekitaldia ezabatu da";
"The event \"%{Summary}\" was updated" = "\"%{Summary}\" ekitaldia eguneratu da";
"The following attendees(s) were notified:" = "Ondorengo partaidea(k) jakinarazi d(ir)a:";
"The following attendees(s) were added:" = "Ondorengo partaidea(k) gehitu d(ir)a:";
"The following attendees(s) were removed:" = "Ondorengo partaidea(k) ezabatu d(ir)a:";
"The following attendees(s) were notified" = "Ondorengo partaidea(k) jakinarazi d(ir)a";
"The following attendees(s) were added" = "Ondorengo partaidea(k) gehitu d(ir)a";
"The following attendees(s) were removed" = "Ondorengo partaidea(k) ezabatu d(ir)a";
/* IMIP messages */
"calendar_label" = "Egutegia";
"startDate_label" = "Hasi:";
"endDate_label" = "Amaitu:";
"startDate_label" = "Hasi";
"endDate_label" = "Amaitu";
"due_label" = "Epemuga:";
"location_label" = "Kokapena:";
"location_label" = "Kokapena";
"summary_label" = "Laburpena:";
"comment_label" = "Iruzkina:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Ekitaldirako gonbidapena: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(%{SentBy}-k bidalia) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}.-ra\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDeskribapena: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}-ra \n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nEnd: %{EndDate}-an, %{EndTime}-tan\nDeskribapena: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Ekitaldia bertan behera utzi da: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} ekitaldi hau bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDEskribapena: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}-k honako ekitaldia bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nAmaiera: %{EndDate}-an %{EndTime}-tan\nDeskribapena: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "%{OldStartDate} eguneko \"%{Summary}\" hitzordua aldatu da";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
= " \"%{Summary}\" bilerako ondorengo parametroak aldatu dira:";
"Please accept or decline those changes."
= "Mesedez, onartu edo ezetsi ondorengo aldaketak.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Gonbidapena onartua: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Ezetzitako gonbidapena: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
= "%{Attendee} %{SentByText}-k zure gonbidapena %{Delegate}-ri delegatu dio.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}-k ez du oraindik zure gonbidapenari buruz erabakirik hartu.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ezin da honako baliabidea atzitu: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" baliabidearentzako gehienezko erreserba kopurura (%{NumberOfSimultaneousBookings}) iritsi gara. Gatazka sortu duen ekitaldia \"%{EventTitle}\" da, eta %{StartDate}-n hasten da.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Evento Confidencial)";
vtodo_class0 = "(Tarefa Pública)";
vtodo_class1 = "(Tarefa Privada)";
vtodo_class2 = "(Tarefa Confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "O evento \"%{Summary}\" foi criado";
"The event \"%{Summary}\" was deleted" = "O evento \"%{Summary}\" foi removido";
"The event \"%{Summary}\" was updated" = "O evento \"%{Summary}\" foi atualizado";
"The following attendees(s) were notified:" = "Estes participantes foram notificados:";
"The following attendees(s) were added:" = "Estes participantes foram adicionados:";
"The following attendees(s) were removed:" = "Estes participantes foram removidos:";
"The following attendees(s) were notified" = "Estes participantes foram notificados";
"The following attendees(s) were added" = "Estes participantes foram adicionados";
"The following attendees(s) were removed" = "Estes participantes foram removidos";
/* IMIP messages */
"calendar_label" = "Calendário:";
"startDate_label" = "Início:";
"endDate_label" = "Fim:";
"calendar_label" = "Calendário";
"startDate_label" = "Início";
"endDate_label" = "Fim";
"due_label" = "Data de Vencimento:";
"location_label" = "Local:";
"location_label" = "Localização";
"summary_label" = "Resumo:";
"comment_label" = "Comentário:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Convite do Evento: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviado por %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}convidou você para %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} convidou você para %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Evento Cancelado: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}cancelou este evento: %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} cancelou este evento: %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "O compromisso \"%{Summary}\" de %{OldStartDate} mudou";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
= "Os seguintes parâmetros mudaram na reunião \"%{Summary}\" :\n\n";
"Please accept or decline those changes."
= "Por favor, aceitar ou recusar as alterações.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Convite aceito: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Convite recusado: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
= "%{Attendee} %{SentByText} delegou o convite para %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}foi ainda não decidiu seu convite ao evento.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Não foi possível acessar o recurso: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "O número máximo de reservas simultaneas (%{NumberOfSimultaneousBookings}) acabou para o recurso \"%{Cn} %{SystemEmail}\". O evento conflitante é \"%{EventTitle}\", e inicia em %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Esdeveniment confidencial)";
vtodo_class0 = "(Tasca pública)";
vtodo_class1 = "(Tasca privada)";
vtodo_class2 = "(Tasca confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Es va crear l'esdeveniment \"%{Summary}\" ";
"The event \"%{Summary}\" was deleted" = "Es va esborrar l'esdeveniment \"%{Summary}\" ";
"The event \"%{Summary}\" was updated" = "Es va modificar l'esdeveniment \"%{Summary}\" ";
"The following attendees(s) were notified:" = "I va notificar als següents participants: ";
"The following attendees(s) were added:" = "Es van agregar els següents participants: ";
"The following attendees(s) were removed:" = "Heu suprimit els assistents següents:";
"The following attendees(s) were notified" = "I va notificar als següents participants ";
"The following attendees(s) were added" = "S'han afegit els següents participants";
"The following attendees(s) were removed" = "Heu suprimit els assistents següents";
/* IMIP messages */
"calendar_label" = "Calendari:";
"startDate_label" = "Inici:";
"endDate_label" = "Final:";
"due_label" = "Data límit:";
"location_label" = "Lloc:";
"calendar_label" = "Calendari";
"startDate_label" = "Inici";
"endDate_label" = "Final";
"due_label" = "Data de venciment";
"location_label" = "Lloc";
"summary_label" = "Resum:";
"comment_label" = "Comentari:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Invitació a l'esdeveniment: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviada per %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Esdeveniment suspès: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "La cita \"%{Summary}\" per al dia %{OldStartDate} ha canviat";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tasca confidencial)";
= "Els paràmetres següents han canviat en la reunió \"%{Summary}\":";
"Please accept or decline those changes."
= "Si us plau, accepteu o rebutgeu aquests canvis.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Invitació acceptada: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Invitació rebutjada; \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tasca confidencial)";
= "%{Attendee} %{SentByText}ha delegat la invitació en %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}encara no s'ha decidit sobre la invitació a l'esdeveniment.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "No es pot accedir al recurs: \"%{Cn} %{SystemEmail}\" ";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Nombre màxim de reserves simultànies (%{NumberOfSimultaneousBookings}) assolit per al recurs \"%{Cn} %{SystemEmail}\". L'esdeveniment que entra en conflicte és \"%{EventTitle}\", i comença el %{StartDate}.";
@@ -0,0 +1,60 @@
"Inviting the following persons is prohibited:" = "禁止邀請以下人士:";
"Personal Calendar" = "個人行事曆";
vevent_class0 = "(公開事件)";
vevent_class1 = "(私人事件)";
vevent_class2 = "(機密事件)";
vtodo_class0 = "(公開任務)";
vtodo_class1 = "(私人任務)";
vtodo_class2 = "(機密任務)";
/* Receipts */
"The event \"%{Summary}\" was created" = "事件 \"%{Summary}\" 已建立";
"The event \"%{Summary}\" was deleted" = "事件\"%{Summary}\" 已刪除";
"The event \"%{Summary}\" was updated" = "事件\"%{Summary}\" 已異動";
"The following attendees(s) were notified" = "已通知下列出席者";
"The following attendees(s) were added" = "已增加下列出席者";
"The following attendees(s) were removed" = "已移除下列出席者";
/* IMIP messages */
"calendar_label" = "行事曆";
"startDate_label" = "開始";
"endDate_label" = "結束";
"due_label" = "截止日期:";
"location_label" = "地點";
"summary_label" = "事件主題:";
"comment_label" = "備註:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "邀請事件: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(來自 %{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} 邀請您參加 %{Summary}。\n\n起始日期: %{StartDate}\n結束日期: %{EndDate}\n備註說明: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} 邀請您參加 %{Summary}。\n\n起始時間: %{StartDate} %{StartTime}\n結束時間: %{EndDate} %{EndTime}\n備註說明: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "取消事件 : \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} 己取消事件: %{Summary}。\n\n起始日期: %{StartDate}\n結束日期: %{EndDate}\n備註說明: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} 己取消了事件: %{Summary}。\n\n起始時間: %{StartDate} %{StartTime}\n結束時間: %{EndDate} %{EndTime}\n備註說明: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= " 安排於%{OldStartDate} 的事件 \"%{Summary}\" 已變更";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "安排於 %{OldStartDate} %{OldStartTime} 的事件 \"%{Summary}\" 已變更";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "已變更會議 \"%{Summary}\" 下列參數:";
"Please accept or decline those changes."
= "請接受或拒絶這些變更。";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "接受邀請: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "拒絶邀請: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "\"委派出席: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "未處理的邀請: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "\"%{Attendee} %{SentByText}已接受了您的邀請。";
"%{Attendee} %{SentByText}has declined your event invitation."
= "\"%{Attendee} %{SentByText}已拒絶了您的邀請。";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "\"%{Attendee} %{SentByText}已將您的邀請委任給%{Delegate}。";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}還沒有決定是否接受您的邀請。";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "無法讀取資源: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" 的衝突事件己達上限 ( %{NumberOfSimultaneousBookings} )。衝突事件 \"%{EventTitle}\" 的起始日期為 %{StartDate}。";
@@ -7,37 +7,32 @@ vevent_class2 = "(Skrytá událost)";
vtodo_class0 = "(Veřejný úkol)";
vtodo_class1 = "(Soukromý úkol)";
vtodo_class2 = "(Skrytý úkol)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Událost \"%{Summary}\" byla vytvořena";
"The event \"%{Summary}\" was deleted" = "Událost \"%{Summary}\" byla smazána";
"The event \"%{Summary}\" was updated" = "Událost \"%{Summary}\" byla upravena";
"The following attendees(s) were notified:" = "Následující účastníci byli upozorněni:";
"The following attendees(s) were added:" = "Následující účastníci byli přidáni:";
"The following attendees(s) were removed:" = "Následující účastníci byli odebráni:";
"The following attendees(s) were notified" = "Následující účastníci byli upozorněni";
"The following attendees(s) were added" = "Následující účastníci byli přidáni";
"The following attendees(s) were removed" = "Následující účastníci byli odebráni";
/* IMIP messages */
"calendar_label" = "Kalendář:";
"startDate_label" = "Začátek:";
"endDate_label" = "Konec:";
"calendar_label" = "Kalendář";
"startDate_label" = "Začátek";
"endDate_label" = "Konec";
"due_label" = "Termín:";
"location_label" = "Místo:";
"location_label" = "Místo";
"summary_label" = "Název:";
"comment_label" = "Komentář:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Pozvání na událost: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(poslal/a %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Zrušení události: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Schůzka \"%{Summary}\" na %{OldStartDate} byla změněna";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Skrytý úkol)";
= "Následující parametry byly ve schůzce \"%{Summary}\" změněny:";
"Please accept or decline those changes."
= "Prosím přijměte nebo odmítněte tyto změny.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Přijaté pozvání: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Odmítnuté pozvání: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Skrytý úkol)";
= "%{Attendee} %{SentByText}delegoval/a Vaše pozvání na %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}dosud o Vašem pozvání k události nerozhodl/a.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Nedostupný zdroj: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Byl dosažen maximální počet současných rezervací\n(%{NumberOfSimultaneousBookings}) pro zdroj \"%{Cn} %{SystemEmail}\". Konfliktní událost je \"%{EventTitle}\" a začíná %{StartDate}.";
@@ -6,37 +6,32 @@ vevent_class2 = "(Hemmelig begivenhed)";
vtodo_class0 = "(Offentlig opgave)";
vtodo_class1 = "(Privat opgave)";
vtodo_class2 = "(Hemmelig opgave)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Begivenheden \"%{Summary}\" blev oprettet";
"The event \"%{Summary}\" was deleted" = "Begivenheden \"%{Summary}\" blev slettet";
"The event \"%{Summary}\" was updated" = "Begivenheden \"%{Summary}\" blev opdateret";
"The following attendees(s) were notified:" = "Følgende deltager(e) blev underrettet:";
"The following attendees(s) were added:" = "Følgende deltager(e) blev tilføjet:";
"The following attendees(s) were removed:" = "Følgende deltager(e) blev fjernet:";
"The following attendees(s) were notified" = "Følgende deltager(e) blev underrettet";
"The following attendees(s) were added" = "Følgende deltager(e) blev tilføjet";
"The following attendees(s) were removed" = "Følgende deltager(e) blev fjernet";
/* IMIP messages */
"calendar_label" = "Kalender:";
"startDate_label" = "Start:";
"endDate_label" = "Slut:";
"calendar_label" = "Kalender";
"startDate_label" = "Start";
"endDate_label" = "Slut";
"due_label" = "Forfaldsdato:";
"location_label" = "Sted:";
"location_label" = "Sted";
"summary_label" = "Resumé:";
"comment_label" = "Kommentér:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Begivenhed Invitation: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(Sendt af%{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}har inviteret dig til %{Summary}.\n\nStart: %{StartDate}\nSlut: %{EndDate}\nBeskrivelse: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer}%{SentByText} har inviteret dig til%{Summary}.\n\nStart:%{StartDate}ved%{StartTime}\nEnd:%{EndDate}ved%{endTime}\nDescription:%{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Begivenhed Annulleret: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}har aflyst denne begivenhed: %{Summary}.\n\nStart: %{StartDate}\nSlut: %{EndDate}\nBeskrivelse: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}har annulleret denne begivenhed: %{Summary}.\n\nStart: %{StartDate} %{StartTime}\nSlut: %{EndDate} %{EndTime}\nBeskrivelse: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Aftalen \"%{Summary}\" for %{OldStartDate} er ændret";
@@ -46,7 +41,6 @@ vtodo_class2 = "(Hemmelig opgave)";
= "Følgende parametre har ændret sig i \"%{Summary}\" møde:";
"Please accept or decline those changes."
= "Venligst acceptér eller afvis disse ændringer.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Accepterede invitationen: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Afslåede invitationen: \"%{Summary}\"";
@@ -60,7 +54,6 @@ vtodo_class2 = "(Hemmelig opgave)";
= "%{Attendee} %{SentByText} har delegeret en invitation til %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} har endnu ikke taget stilling til din begivenhedsinvitation.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kan ikke tilgå ressourcen: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimalt antal tilladte indkaldelser (%{NumberOfSimultaneousBookings}) er nået for ressource \"%{Cn} %{SystemEmail}\". Konflikter med begivenheden \"%{EventTitle}\", som starter %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Vertrouwelijke afspraak)";
vtodo_class0 = "(Publieke taak)";
vtodo_class1 = "(Privétaak)";
vtodo_class2 = "(Vertrouwelijke taak)";
/* Receipts */
"The event \"%{Summary}\" was created" = "De gebeurtenis \"%{Summary}\" is aangemaakt";
"The event \"%{Summary}\" was deleted" = "De gebeurtenis \"%{Summary}\" is verwijderd";
"The event \"%{Summary}\" was updated" = "De gebeurtenis \"%{Summary}\" is gewijzigd";
"The following attendees(s) were notified:" = "De volgende deelnemers zijn in kennis gesteld:";
"The following attendees(s) were added:" = "De volgende deelnemers zijn toegevoegd:";
"The following attendees(s) were removed:" = "De volgende deelnemers zijn verwijderd:";
"The following attendees(s) were notified" = "De volgende deelnemers zijn in kennis gesteld";
"The following attendees(s) were added" = "De volgende deelnemers zijn toegevoegd";
"The following attendees(s) were removed" = "De volgende deelnemers zijn verwijderd";
/* IMIP messages */
"calendar_label" = "Agenda:";
"startDate_label" = "Start:";
"endDate_label" = "Einde:";
"calendar_label" = "Agenda";
"startDate_label" = "Start";
"endDate_label" = "Einde";
"due_label" = "Vervaldag:";
"location_label" = "Locatie:";
"location_label" = "Locatie";
"summary_label" = "Samenvatting:";
"comment_label" = "Commentaar:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Uitnodiging voor gebeurtenis: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(Verzonden door %{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}heeft u uitgenodigd voor: %{Summary}.\n\nBegin: %{StartDate}\nEinde: %{EndDate}\nBeschrijving: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}heeft u uitgenodigd voor %{Summary}.\n\nStart: %{StartDate} om %{StartTime}\nEinde: %{EndDate} om %{EndTime}\nOmschrijving: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Gebeurtenis geannuleerd: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}heeft deze gebeurtenis afgezegd: %{Summary}.\n\nBegin: %{StartDate}\nEinde: %{EndDate}\nBeschrijving: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}heeft de gebeurtenis: %{Summary} geannuleerd.\n\nStart: %{StartDate} om %{StartTime}\nEinde: %{EndDate} om %{EndTime}\nOmschrijving: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "De gebeurtenis \"%{Summary}\" op %{OldStartDate} is veranderd.";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
= "De volgende parameters zijn veranderd in de \"%{Summary}\" vergadering:";
"Please accept or decline those changes."
= "Accepteer deze wijzigingen of wijs ze af.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Uitnodiging aanvaard: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Uitnodiging afgewezen: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
= "%{Attendee} %{SentByText}heeft de uitnodiging gedelegeerd aan %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}heeft nog niet gereageerd op uw uitnodiging.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kan bron \"%{Cn} %{SystemEmail}\" niet benaderen";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Het maximale aantal gelijktijdige reserveringen (%{NumberOfSimultaneousBookings}) voor \"%{Cn} %{SystemEmail}\" is bereikt. De conflicterende gebeurtenis is \"%{EventTitle}\" en begint op %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Confidential event)";
vtodo_class0 = "(Public task)";
vtodo_class1 = "(Private task)";
vtodo_class2 = "(Confidential task)";
/* Receipts */
"The event \"%{Summary}\" was created" = "The event \"%{Summary}\" was created";
"The event \"%{Summary}\" was deleted" = "The event \"%{Summary}\" was deleted";
"The event \"%{Summary}\" was updated" = "The event \"%{Summary}\" was updated";
"The following attendees(s) were notified:" = "The following attendee(s) were notified:";
"The following attendees(s) were added:" = "The following attendee(s) were added:";
"The following attendees(s) were removed:" = "The following attendee(s) were removed:";
"The following attendees(s) were notified" = "The following attendee(s) were notified";
"The following attendees(s) were added" = "The following attendee(s) were added";
"The following attendees(s) were removed" = "The following attendee(s) were removed";
/* IMIP messages */
"calendar_label" = "Calendar:";
"startDate_label" = "Start:";
"endDate_label" = "End:";
"due_label" = "Due Date:";
"location_label" = "Location:";
"summary_label" = "Summary:";
"comment_label" = "Comment:";
"calendar_label" = "Calendar";
"startDate_label" = "Start";
"endDate_label" = "End";
"due_label" = "Due Date";
"location_label" = "Location";
"summary_label" = "Summary";
"comment_label" = "Comment";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Event Invitation: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(sent by %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Event Cancelled: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "The appointment \"%{Summary}\" for the %{OldStartDate} has changed";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Confidential task)";
= "The following parameters have changed in the \"%{Summary}\" meeting:";
"Please accept or decline those changes."
= "Please accept or decline those changes.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Accepted invitation: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Declined invitation: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Confidential task)";
= "%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}has not yet decided upon your event invitation.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Cannot access resource: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Luottamuksellinen tapahtuma)";
vtodo_class0 = "(Julkinen tehtävä)";
vtodo_class1 = "(Yksityinen tehtävä)";
vtodo_class2 = "(Luottamuksellinen tehtävä)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Tapahtuma \"%{Summary}\" luotiin";
"The event \"%{Summary}\" was deleted" = "Tapahtuma \"%{Summary}\" poistettiin";
"The event \"%{Summary}\" was updated" = "Tapahtuma \"%{Summary}\" päivitettiin";
"The following attendees(s) were notified:" = "Seuraava(t) osallistuja(t) on informoitu:";
"The following attendees(s) were added:" = "Seuraava(t) osalllistuja(t) on lisätty:";
"The following attendees(s) were removed:" = "Seuraava(t) osalllistuja(t) poistettiin:";
"The following attendees(s) were notified" = "Seuraava(t) osallistuja(t) on informoitu";
"The following attendees(s) were added" = "Seuraava(t) osalllistuja(t) on lisätty";
"The following attendees(s) were removed" = "Seuraava(t) osalllistuja(t) poistettiin";
/* IMIP messages */
"calendar_label" = "Kalenteri:";
"startDate_label" = "Alkaa:";
"endDate_label" = "Päättyy:";
"calendar_label" = "Kalenteri";
"startDate_label" = "Alkaa";
"endDate_label" = "Päättyy";
"due_label" = "Määräpäivä:";
"location_label" = "Sijainti:";
"location_label" = "Sijainti";
"summary_label" = "Yhteenveto:";
"comment_label" = "Kommentti:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Tapahtumakutsu: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(lähettäjä %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}on kutsunut sinut %{Summary}n.⏎ ⏎ Alkaa: %{StartDate}⏎ Loppuu: %{EndDate}⏎ Kuvaus: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}on kutsunut sinut %{Summary}n.⏎ ⏎ Alkaa: %{StartDate} klo %{StartTime}⏎ Loppuu: %{EndDate} klo %{EndTime}⏎ Kuvaus: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Tapahtuma peruttu: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}on perunut tapahtuman: %{Summary}.⏎ ⏎ Alkaa: %{StartDate}⏎ Loppuu: %{EndDate}⏎ Kuvaus: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}on peruuttanut tapahtuman: %{Summary}.⏎ ⏎ Alkaa: %{StartDate}klot %{StartTime}⏎ Loppuu: %{EndDate} klo %{EndTime}⏎ Kuvaus: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Tapaaminen \"%{Summary}\" %{OldStartDate} on muuttunut";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
= "Seuraavat tiedot ovat muuttuneet tapaamisessa \"%{Summary}\":";
"Please accept or decline those changes."
= "Ole hyvä ja hyväksy tai hylkää muutokset.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Hyväksytty kutsu: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Hylätty kutsu: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
= "%{Attendee} %{SentByText} on lähettänyt kutsusi edelleen osoitteeseen %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} ei vielä ole tehnyt päätöstä tapahtumakutsustasi.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ei pääsyä resurssiin: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimimäärä yhtäaikaisia varauksia\n(%{NumberOfSimultaneousBookings}) saavutettu resurssille \"%{Cn} %{SystemEmail}\". Ristiriitainen tapahtuma: \"%{EventTitle}\" alkaa %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Événement confidentiel)";
vtodo_class0 = "(Tâche publique)";
vtodo_class1 = "(Tâche privée)";
vtodo_class2 = "(Tâche confidentielle)";
/* Receipts */
"The event \"%{Summary}\" was created" = "L'événement «%{Summary}» a été créé";
"The event \"%{Summary}\" was deleted" = "L'événement «%{Summary}» a été effacé";
"The event \"%{Summary}\" was updated" = "L'événement «%{Summary}» a été modifié";
"The following attendees(s) were notified:" = "Les invités suivants ont été avisés :";
"The following attendees(s) were added:" = "Les invités suivants ont été ajoutés :";
"The following attendees(s) were removed:" = "Les invités suivants ont été supprimés :";
"The following attendees(s) were notified" = "Les invités suivants ont été avisés ";
"The following attendees(s) were added" = "Les invités suivants ont été ajoutés ";
"The following attendees(s) were removed" = "Les invités suivants ont été supprimés ";
/* IMIP messages */
"calendar_label" = "Agenda :";
"startDate_label" = "Début :";
"endDate_label" = "Fin :";
"calendar_label" = "Agenda ";
"startDate_label" = "Début ";
"endDate_label" = "Fin ";
"due_label" = "Fin prévue :";
"location_label" = "Lieu :";
"location_label" = "Lieu ";
"summary_label" = "Titre :";
"comment_label" = "Description :";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Invitation à la réunion : «%{Summary}»";
"(sent by %{SentBy}) " = "(envoyé par %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vous a invité à la réunion « %{Summary} ».\n\nDébut: %{StartDate}\nFin: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vous a invité à la réunion « %{Summary} ».\n\nDébut: %{StartDate} à %{StartTime}\nFin: %{EndDate} à %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Réunion annulée : « %{Summary} »";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}a annulé cette réunion : « %{Summary} ».\n\nDébut: %{StartDate}\nFin: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}a annulé cette réunion : « %{Summary} ».\n\nDébut: %{StartDate} à %{StartTime}\nFin: %{EndDate} à %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "La réunion « %{Summary} » de %{OldStartDate} a été modifiée";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tâche confidentielle)";
= "Les paramètres suivants ont été modifiés pour la réunion « %{Summary} » :";
"Please accept or decline those changes."
= "Veuillez reconfirmer ou annuler votre présence.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Invitation acceptée : «%{Summary}»";
"Declined invitation: \"%{Summary}\"" = "Invitation refusée : «%{Summary}»";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tâche confidentielle)";
= "%{Attendee} %{SentByText}a délégué votre invitation à %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}choisit de reporter sa décision par rapport à votre invitation.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Impossible d'accéder à la ressource suivante: %{Cn} %{SystemEmail}";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Le nombre maximal de réservations simultanées (%{NumberOfSimultaneousBookings}) est atteint pour la ressource «%{Cn} %{SystemEmail}». L'événement en conflit est «%{EventTitle}» et débute le %{StartDate}.";
+1 -1
View File
@@ -54,7 +54,7 @@ Appointments_RESOURCE_FILES += \
MSExchangeFreeBusySOAPResponseMap.plist \
MSExchangeFreeBusySOAPRequest.wo
Appointments_LANGUAGES = Arabic Basque BrazilianPortuguese Catalan Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian NorwegianBokmal NorwegianNynorsk Polish Russian Slovak SpanishSpain SpanishArgentina Swedish Ukrainian Welsh
Appointments_LANGUAGES = Arabic Basque BrazilianPortuguese Catalan ChineseTaiwan Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian Macedonian NorwegianBokmal NorwegianNynorsk Polish Portuguese Russian Slovak Slovenian SpanishSpain SpanishArgentina Swedish Ukrainian Welsh
Appointments_LOCALIZED_RESOURCE_FILES = Localizable.strings
@@ -7,37 +7,32 @@ vevent_class2 = "(Vertraulicher Termin)";
vtodo_class0 = "(Öffentliche Aufgabe)";
vtodo_class1 = "(Private Aufgabe)";
vtodo_class2 = "(Vertrauliche Aufgabe)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Der Termin \"%{Summary}\" wurde angelegt";
"The event \"%{Summary}\" was deleted" = "Der Termin \"%{Summary}\" wurde gelöscht";
"The event \"%{Summary}\" was updated" = "Der Termin \"%{Summary}\" wurde geändert";
"The following attendees(s) were notified:" = "Die folgenden Teilnehmer wurden benachrichtigt:";
"The following attendees(s) were added:" = "Die folgenden Teilnehmer wurden hinzugefügt:";
"The following attendees(s) were removed:" = "Die folgenden Teilnehmer wurden entfernt:";
"The following attendees(s) were notified" = "Die folgenden Teilnehmer wurden benachrichtigt";
"The following attendees(s) were added" = "Die folgenden Teilnehmer wurden hinzugefügt";
"The following attendees(s) were removed" = "Die folgenden Teilnehmer wurden entfernt";
/* IMIP messages */
"calendar_label" = "Kalender:";
"startDate_label" = "Beginn:";
"endDate_label" = "Ende:";
"calendar_label" = "Kalender";
"startDate_label" = "Beginn";
"endDate_label" = "Ende";
"due_label" = "Fälligkeit:";
"location_label" = "Ort:";
"location_label" = "Ort";
"summary_label" = "Zusammenfassung:";
"comment_label" = "Kommentar:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Termineinladung: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(gesendet von %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} Hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate} um %{StartTime}\nEnde: %{EndDate} um %{EndTime}\nBeschreibung: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Termin abgesagt: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} hat den folgenden Termin abgesagt: \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} hat diesen Termin abgesagt: \"%{Summary}\".\n\nBeginn: %{StartDate} um %{StartTime}\nEnde: %{EndDate} um %{EndTime}\nBeschreibung: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Die Verabredung \"%{Summary}\" am %{OldStartDate} wurde geändert";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
= "Folgendes wurde am Termin \"%{Summary}\" geändert:";
"Please accept or decline those changes."
= "Bitte akzeptieren Sie diese Änderung oder lehnen Sie diese ab.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Einladung zugestimmt: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Einladung abgelehnt: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
= "%{Attendee} %{SentByText} hat die Einladung delegiert an %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} hat noch nicht über Ihre Termineinladung entschieden.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kann auf folgende Ressource nicht zugreifen: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Die maximale Anzahl von gleichzeitigen Buchungen (%{NumberOfSimultaneousBookings}) für die Ressource \"%{Cn} %{SystemEmail}\" ist erreicht. Der kollidierende Termin ist \"%{EventTitle}\", und beginnt am %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Bizalmas esemény)";
vtodo_class0 = "(Nyilvános feladat)";
vtodo_class1 = "(Magán feladat)";
vtodo_class2 = "(Bizalmas feladat)";
/* Receipts */
"The event \"%{Summary}\" was created" = "A \"%{Summary}\" esemény létre lett hozva";
"The event \"%{Summary}\" was deleted" = "A \"%{Summary}\" esemény törölve lett";
"The event \"%{Summary}\" was updated" = "A \"%{Summary}\" esemény módosítva lett";
"The following attendees(s) were notified:" = "Az alábbi résztvevők lettek értesítve:";
"The following attendees(s) were added:" = "Az alábbi résztvevők lettek hozzáadva:";
"The following attendees(s) were removed:" = "Az alábbi résztvevők lettek eltávolítva:";
"The following attendees(s) were notified" = "Az alábbi résztvevők lettek értesítve";
"The following attendees(s) were added" = "Az alábbi résztvevők lettek hozzáadva";
"The following attendees(s) were removed" = "Az alábbi résztvevők lettek eltávolítva";
/* IMIP messages */
"calendar_label" = "Naptár:";
"startDate_label" = "Kezdete:";
"endDate_label" = "Vége:";
"calendar_label" = "Naptár";
"startDate_label" = "Kezdete";
"endDate_label" = "Vége";
"due_label" = "Lejárat napja:";
"location_label" = "Hely:";
"location_label" = "Hely";
"summary_label" = "Összegzés:";
"comment_label" = "Megjegyzés:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Meghívás eseményre: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(%{SentBy} által elküldve) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} meghívta Önt a(z) %{Summary} tárgyú eseményre.\n\nKezdete: %{StartDate}: \nVége: %{EndDate}\nLeírása: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} meghívta Önt erre az eseményre: %{Summary}.\n\nKezdete: %{StartDate} at %{StartTime}\nVége: %{EndDate} at %{EndTime}\nLeírás: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Az esemény törölve lett: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} törlte az alábbi eseményt: %{Summary}.\n\nKzdete: %{StartDate}\nVége: %{EndDate}\nLeírás: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} törölte az alábbi eseményt: %{Summary}.\n\nKezdete: %{StartDate} at %{StartTime}\nVége: %{EndDate} at %{EndTime}\nLeírás: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "A(z) \"%{Summary}\" tárgyú, %{OldStartDate} időpontra tervezett esemény módosult.";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Bizalmas feladat)";
= "A \"%{Summary}\" tárgyú találkozó következő adatai módosultak:";
"Please accept or decline those changes."
= "Kérem fogadja el vagy utasítsa vissza a változásokat.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Elfogadott meghivás: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Elutasított meghívás: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Bizalmas feladat)";
= "%{Attendee} %{SentByText} átruházta a meghívást az alábbi személynek %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}még meggondolja a meghívását.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Az alábbi elem nem elérhető: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "A maximális egyidejű foglalás számát (%{NumberOfSimultaneousBookings}) elérte a(z) \"%{Cn} %{SystemEmail}\" elem esetében. Az ütköző esemény a(z) \"%{EventTitle}\", a kezdeti időpontja %{StartDate}.";
@@ -6,42 +6,34 @@ vevent_class2 = "(Viðburður er trúnaðarmál)";
vtodo_class0 = "(Almennt verkefni)";
vtodo_class1 = "(Einkaverkefni)";
vtodo_class2 = "(Verkefni er trúnaðarmál)";
/* Receipts */
"Title:" = "Titill:";
"Start:" = "Byrjun:";
"End:" = "Endir:";
"Receipt: users invited to a meeting" = "Móttakandi: notendur boðnir á fund";
"You have invited the following attendees(s):" = "Eftirfarandi þáttakanda/þáttakendum hefur verið boðið:";
"... to attend the following event:" = "... að vera viðstaddir eftirfarandi viðburð:";
"Receipt: invitation updated" = "Móttakandi: uppfærður boðsmiði";
"The following attendees(s):" = "Eftirfarandi þáttakandi/Þáttakendur:";
"... have been notified of the changes to the following event:" = "... hafa fengið upplýsingar um breytingarnar á eftirfarandi viðburði:";
"Receipt: attendees removed from an event" = "Móttakandi: þáttakendur fjarlægðir af viðburði";
"You have removed the following attendees(s):" = "Eftirfarandi þáttakandi/þáttakendur hafa verið fjarlægðir:";
"... from the following event:" = "... af eftirfarandi viðburði:";
/* IMIP messages */
"startDate_label" = "Byrjun:";
"endDate_label" = "Endir:";
"startDate_label" = "Byrjun";
"endDate_label" = "Endir";
"due_label" = "Lokadagur:";
"location_label" = "Staðsetning:";
"location_label" = "Staðsetning";
"summary_label" = "Samantekt:";
"comment_label" = "Athugasemd:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Boð á viðburð: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(%{SentBy} sendi þetta) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}hefur boðið þér á þennan viðburð: %{Summary}.\n\nByrjun: %{StartDate} at %{StartTime}\nEndir: %{EndDate} at %{EndTime}\nLýsing: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Viðburði aflýst: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}hefur aflýst þessum viðburði: %{Summary}.\n\nByrjun: %{StartDate} kl. %{StartTime}\nEndir: %{EndDate} kl. %{EndTime}\nLýsing: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "Tímapöntunin \"%{Summary}\" sem var þann %{OldStartDate} kl. %{OldStartTime} hefur breyst.";
@@ -49,7 +41,6 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
= "Eftirfarandi atriði hafa breyst varðandi fundinn: \"%{Summary}\"";
"Please accept or decline those changes."
= "Þessar breytingar þarf að samþykkja eða hafna.";
/* Reply */
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}hefur samþykkt boð þitt á viðburð.";
@@ -59,6 +50,5 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
= "%{Attendee} %{SentByText}hefur skipað sem fulltrúa sinn %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}hefur ekki enn tekið ákvörðun varðandi boð þitt.";
/* Resources */
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Hámarksfjöldi samtímabókana (%{NumberOfSimultaneousBookings}) hefur verið náð varðandi aðfangið \"%{Cn} %{SystemEmail}\".";
@@ -6,42 +6,34 @@ vevent_class2 = "(Evento confidenziale)";
vtodo_class0 = "(Attività pubblica)";
vtodo_class1 = "(Attività privata)";
vtodo_class2 = "(Attività confidenziale)";
/* Receipts */
"Title:" = "Titolo: ";
"Start:" = "Inizio: ";
"End:" = "Fine: ";
"Receipt: users invited to a meeting" = "Notifica: utenti invitati al meeting";
"You have invited the following attendees(s):" = "Hai invitato i seguenti partecipanti:";
"... to attend the following event:" = "... a partecipare al seguente evento:";
"Receipt: invitation updated" = "Notifica: invito aggiornato";
"The following attendees(s):" = "I seguenti partecipanti:";
"... have been notified of the changes to the following event:" = "... hanno ricevuto una notifica di cambiamento relativa al seguente evento:";
"Receipt: attendees removed from an event" = "Notifica: partecipanti rimossi dall'evento";
"You have removed the following attendees(s):" = "Hai rimosso i seguenti partecipanti:";
"... from the following event:" = "... dal seguente evento:";
/* IMIP messages */
"startDate_label" = "Inizio:";
"endDate_label" = "Fine:";
"startDate_label" = "Inizio";
"endDate_label" = "Fine";
"due_label" = "Scadenza:";
"location_label" = "Luogo:";
"location_label" = "Luogo";
"summary_label" = "Summario:";
"comment_label" = "Commento:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Invito Evento: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(inviato da %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}ti ha invitato al %{Summary}.\n\nInizio: %{StartDate} alle %{StartTime}\nFine: %{EndDate} alle %{EndTime}\nDescrizione: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Evento cancellato: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ha cancellato questo evento: %{Summary}.\n\nInizio: %{StartDate} alle %{StartTime}\nFine: %{EndDate} alle %{EndTime}\nDescrizione: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "L'appuntamento \"%{Summary}\" per la data %{OldStartDate} all'ora %{OldStartTime} è cambiato";
@@ -49,7 +41,6 @@ vtodo_class2 = "(Attività confidenziale)";
= "I seguenti dati sono cambiati nell'appuntamento \"%{Summary}\" :";
"Please accept or decline those changes."
= "Prego accettare o rifiutare questi cambiamenti";
/* Reply */
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}ha accettato il tuo invito al evento.";
@@ -59,6 +50,5 @@ vtodo_class2 = "(Attività confidenziale)";
= "%{Attendee} %{SentByText}ha delegato l'invito a %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}non ha ancora deciso riguardo il tuo invito all'evento.";
/* Resources */
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Numero massimo di prenotazioni simultanee (%{NumberOfSimultaneousBookings}) raggiunto per la risorsa \"%{Cn} %{SystemEmail}\".";
+1 -1
View File
@@ -344,7 +344,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
NSMutableString *s;
s = [NSMutableString stringWithCapacity: 64];
[s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
[s appendFormat:@"<0x%08X[%@]:", (unsigned int)self, NSStringFromClass([self class])];
if (freeBusyViewType)
[s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType];
if (mergedFreeBusy)
@@ -0,0 +1,60 @@
"Inviting the following persons is prohibited:" = "Канењето на следните лица е забрането:";
"Personal Calendar" = "Личен календар";
vevent_class0 = "(Јавен настан)";
vevent_class1 = "(Приватен настан)";
vevent_class2 = "(Доверлив настан)";
vtodo_class0 = "(Јавна задача)";
vtodo_class1 = "(Приватна задача)";
vtodo_class2 = "(Доверлива задача)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Настанот \"%{Summary}\" беше креиран";
"The event \"%{Summary}\" was deleted" = "Настанот \"%{Summary}\" беше избришан";
"The event \"%{Summary}\" was updated" = "Настанот \"%{Summary}\" беше освежен";
"The following attendees(s) were notified" = "Следните учесници беа известини";
"The following attendees(s) were added" = "Следните учесници беа додадени";
"The following attendees(s) were removed" = "Следните учесници беа отстранети";
/* IMIP messages */
"calendar_label" = "Календар:";
"startDate_label" = "Почеток:";
"endDate_label" = "Крај:";
"due_label" = "Краен датум:";
"location_label" = "Локација:";
"summary_label" = "Резиме:";
"comment_label" = "Коментар:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Покана за настанот: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(испратено од %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}ве покани на %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}ве покани на %{Summary}.\n\nПочеток: %{StartDate} во %{StartTime}\nКрај: %{EndDate} во %{EndTime}\nОпис: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Настанот е откажан: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ја откажа поканата: %{Summary}.\n\nПочеток: %{StartDate}\nКрај: %{EndDate}\nОпис: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}го откажа настанот: %{Summary}.\n\nПочеток: %{StartDate} во %{StartTime}\nКрај: %{EndDate} во %{EndTime}\nОпис: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Состанокот \"%{Summary}\" за %{OldStartDate} е променет";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "Состанокот \"%{Summary}\" за %{OldStartDate} на %{OldStartTime} се промени";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "Следните параметри се променија во \"%{Summary}\" состанок:";
"Please accept or decline those changes."
= "Прифатете ги ли отфрлете ги измените.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Прифатена покана: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Одбиена покана: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Делегирана покана: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "Уште не одлучиле по поканата: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}ја прифати вашата покана.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "%{Attendee} %{SentByText}ја одби вашаа покана.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "%{Attendee} %{SentByText}ја делегираше поканата на %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}уште нема одлучено по вашата покана.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Не можам да ги пристапам ресурсите: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Максималниот број на едновремени закажувања (%{NumberOfSimultaneousBookings}) е постигнат за ресурсот \"%{Cn} %{SystemEmail}\". Конфликтниот настан е \"%{EventTitle}\", и почнува на %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Konfidensiell hendelse)";
vtodo_class0 = "(Offentlig oppgave)";
vtodo_class1 = "(Privat oppgave)";
vtodo_class2 = "(Konfidensiell oppgave)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Hendelsen \"%{Summary}\" ble opprettet";
"The event \"%{Summary}\" was deleted" = "Hendelsen \"%{Summary}\" ble slettet";
"The event \"%{Summary}\" was updated" = "Hendelsen \"%{Summary}\" ble oppdatert";
"The following attendees(s) were notified:" = "Følgende deltaker(e) ble varslet:";
"The following attendees(s) were added:" = "Følgende deltaker(e) ble lagt til:";
"The following attendees(s) were removed:" = "Følgende deltaker(e) ble fjernet:";
"The following attendees(s) were notified" = "Følgende deltaker(e) ble varslet";
"The following attendees(s) were added" = "Følgende deltaker(e) ble lagt til";
"The following attendees(s) were removed" = "Følgende deltaker(e) ble fjernet";
/* IMIP messages */
"calendar_label" = "Kalender:";
"startDate_label" = "Start:";
"endDate_label" = "Slutt:";
"calendar_label" = "Kalender";
"startDate_label" = "Start";
"endDate_label" = "Slutt";
"due_label" = "Forfallsdato:";
"location_label" = "Sted:";
"location_label" = "Sted";
"summary_label" = "Sammendrag:";
"comment_label" = "Kommentar:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Hendelseinvitasjon: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(sendt av %{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} har invitert deg til %{Summary}.\n\nStart: %{StartDate}\nSlutt: %{EndDate}\nBeskrivelse: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} har invitert deg til %{Summary}.\n\nStart: %{StartDate} klokken %{StartTime}\nSlutt: %{EndDate} klokken %{EndTime}\nBeskrivelse: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Hendelse avlyst: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} har avlyst hendelsen: %{Summary}.\n\nStart: %{StartDate}\nSlutt: %{EndDate}\nBeskrivelse: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} har avlyst : %{Summary}.\n\nStart: %{StartDate} klokken %{StartTime}\nEnd: %{EndDate} klokken %{EndTime}\nBeskrivelse: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Avtalen \"%{Summary}\", %{OldStartDate} har endret seg";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Konfidensiell oppgave)";
= "Følgende parametre har endret seg for \"%{Summary}\" møtet:";
"Please accept or decline those changes."
= "Vennligst godta eller avvis endringene.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Aksepterte invitasjonen: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Avviste invitasjonen: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Konfidensiell oppgave)";
= "%{Attendee} %{SentByText} har delegert invitasjonen til %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} har enda ikke svart på invitasjonen din.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Har ikke tilgang til ressurs: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimum antall samtidige reservasjoner (%{NumberOfSimultaneousBookings}) er nådd for ressursen \"%{Cn} %{SystemEmail}\". Den kolliderende hendelsen er \"%{EventTitle}\", og starter %{StartDate}.";
@@ -6,42 +6,34 @@ vevent_class2 = "(Konfidensiell hendelse)";
vtodo_class0 = "(Offentlig oppgave)";
vtodo_class1 = "(Privat oppgave)";
vtodo_class2 = "(Konfidensiell oppgave)";
/* Receipts */
"Title:" = "Tittel:";
"Start:" = "Start:";
"End:" = "Slutt:";
"Receipt: users invited to a meeting" = "Kvittering: brukere invitert til et møte";
"You have invited the following attendees(s):" = "Du har invitert følende deltagere:";
"... to attend the following event:" = "... til å delta i følgende hendelse:";
"Receipt: invitation updated" = "Kvittering: invitasjon oppdatert";
"The following attendees(s):" = "Følgende deltager(e):";
"... have been notified of the changes to the following event:" = "... har blitt informert om endringene i følgende hendelse:";
"Receipt: attendees removed from an event" = "Kvittering: deltagere fjernet fra en hendelse";
"You have removed the following attendees(s):" = "Du har tatt bort følgende deltagere:";
"... from the following event:" = "... fra følgende hendelse:";
/* IMIP messages */
"startDate_label" = "Start:";
"endDate_label" = "End:";
"startDate_label" = "Start";
"endDate_label" = "End";
"due_label" = "Due Date:";
"location_label" = "Location:";
"location_label" = "Location";
"summary_label" = "Summary:";
"comment_label" = "Comment:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Event Invitation: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(sent by %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Event Cancelled: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed";
@@ -49,7 +41,6 @@ vtodo_class2 = "(Konfidensiell oppgave)";
= "The following parameters have changed in the \"%{Summary}\" meeting:";
"Please accept or decline those changes."
= "Please accept or decline those changes.";
/* Reply */
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}has accepted your event invitation.";
@@ -59,6 +50,5 @@ vtodo_class2 = "(Konfidensiell oppgave)";
= "%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}has not yet decided upon your event invitation.";
/* Resources */
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\".";
@@ -7,37 +7,32 @@ vevent_class2 = "(Wydarzenie poufne)";
vtodo_class0 = "(Zadanie publiczne)";
vtodo_class1 = "(Zadanie prywatne)";
vtodo_class2 = "(Zadanie poufne)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Utworzono wydarzenie \"%{Summary}\"";
"The event \"%{Summary}\" was deleted" = "Skasowano wydarzenie \"%{Summary}\"";
"The event \"%{Summary}\" was updated" = "Zaktualizowano wydarzenie \"%{Summary}\"";
"The following attendees(s) were notified:" = "Następujący uczestnicy zostali powiadomieni:";
"The following attendees(s) were added:" = "Następujący uczestnicy zostali dodani:";
"The following attendees(s) were removed:" = "Następujący uczestnicy zostali usunięci:";
"The following attendees(s) were notified" = "Następujący uczestnicy zostali powiadomieni";
"The following attendees(s) were added" = "Następujący uczestnicy zostali dodani";
"The following attendees(s) were removed" = "Następujący uczestnicy zostali usunięci";
/* IMIP messages */
"calendar_label" = "Kalendarz:";
"startDate_label" = "Początek:";
"endDate_label" = "Koniec:";
"due_label" = "Termin:";
"location_label" = "Miejsce:";
"summary_label" = "Podsumowanie:";
"comment_label" = "Komentarz:";
"calendar_label" = "Kalendarz";
"startDate_label" = "Początek";
"endDate_label" = "Koniec";
"due_label" = "Termin";
"location_label" = "Miejsce";
"summary_label" = "Podsumowanie";
"comment_label" = "Komentarz";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Zaproszenie na wydarzenie: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(wysłane przez %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}zaprosił Cię na %{Summary}.⏎ ⏎ Początek: %{StartDate}⏎ Koniec: %{EndDate}⏎ Opis: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}zaprosił cię na %{Summary}.\n\nPoczątek: %{StartDate} o %{StartTime}\nKoniec: %{EndDate} o %{EndTime}\nOpis: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Wydarzenie anulowane: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} anulował(a) to wydarzenie: %{Summary}.⏎⏎ Początek: %{StartDate}⏎ Koniec: %{EndDate}⏎ Opis: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}anulował(a) to wydarzenie: %{Summary}.\n\nPoczątek: %{StartDate} o %{StartTime}\nKoniec: %{EndDate} o %{EndTime}\nOpis: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Wydarzenie \"%{Summary}\" z dnia %{OldStartDate} zmieniło się";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Zadanie poufne)";
= "Zmienione zostały poniższe parametry spotkania \"%{Summary}\":";
"Please accept or decline those changes."
= "Zaakceptuj lub odrzuć te zmiany.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Zaakceptowano zaproszenie: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Odrzucono zaproszenie: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Zadanie poufne)";
= "%{Attendee} %{SentByText}oddelegował(a) %{Delegate} na twoje wydarzenie.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}jeszcze nie zdecydował(a) o obecności na twoim wydarzeniu.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Nie ma dostępu do: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Osiągnięto maksymalną liczbę równoległych rezerwacji (%{NumberOfSimultaneousBookings}) dla \"%{Cn} %{SystemEmail}\". Problem wywołuje wydarzenie \"%{EventTitle}\" zaczynające się w %{StartDate}.";
@@ -0,0 +1,60 @@
"Inviting the following persons is prohibited:" = "Convidando as seguintes pessoas é proibido:";
"Personal Calendar" = "Calendário Pessoal";
vevent_class0 = "(Evento Público)";
vevent_class1 = "(Evento Privado)";
vevent_class2 = "(Evento Confidencial)";
vtodo_class0 = "(Tarefa Pública)";
vtodo_class1 = "(Tarefa Privada)";
vtodo_class2 = "(Tarefa Confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "O evento \"%{Summary}\" foi criado";
"The event \"%{Summary}\" was deleted" = "O evento \"%{Summary}\" foi removido";
"The event \"%{Summary}\" was updated" = "O evento \"%{Summary}\" foi atualizado";
"The following attendees(s) were notified:" = "Os seguintes participantes foram notificados:";
"The following attendees(s) were added:" = "Os seguintes participantes foram adicionados:";
"The following attendees(s) were removed:" = "Os seguintes participantes foram removidos:";
/* IMIP messages */
"calendar_label" = "Calendário:";
"startDate_label" = "Início:";
"endDate_label" = "Fim:";
"due_label" = "Data de Vencimento:";
"location_label" = "Local:";
"summary_label" = "Resumo:";
"comment_label" = "Comentário:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Convite do Evento: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviado por %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}convidou-o para %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} convidou-o para %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Evento Cancelado: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}cancelou este evento: %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} cancelou este evento: %{Summary}.\n\nInício: %{StartDate} às %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "O compromisso \"%{Summary}\" de %{OldStartDate} mudou";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "O Compromisso \"%{Summary}\" de %{OldStartDate} as %{OldStartTime} mudou";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "Os seguintes parâmetros mudaram na reunião \"%{Summary}\" :\n\n";
"Please accept or decline those changes."
= "Por favor, aceitar ou recusar as alterações.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Convite aceite: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Convite recusado: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Convite delegado: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "Convite ainda não decidido: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText} aceitou seu convite ao evento.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "%{Attendee} %{SentByText} rejeitou seu convite ao evento.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "%{Attendee} %{SentByText} delegou o convite para %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} ainda não decidiu seu convite ao evento.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Não foi possível aceder ao recurso: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "O número máximo de reservas simultâneas (%{NumberOfSimultaneousBookings}) acabou para o recurso \"%{Cn} %{SystemEmail}\". O evento em conflito é \"%{EventTitle}\", e inicia em %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Конфиденциальное событие)";
vtodo_class0 = "(Публичная задача)";
vtodo_class1 = "(Личная задача)";
vtodo_class2 = "(Конфиденциальная задача)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Было создано мероприятие \"%{Summary}\"";
"The event \"%{Summary}\" was deleted" = "Мероприятие \"%{Summary}\" было удалено";
"The event \"%{Summary}\" was updated" = "Мероприятие \"%{Summary}\" было обновлено";
"The following attendees(s) were notified:" = "Следующие приглашенные были оповещены:";
"The following attendees(s) were added:" = "Следующие люди были добавлены в список приглашенных:";
"The following attendees(s) were removed:" = "Следующие люди были исключены из списка приглашенных:";
"The following attendees(s) were notified" = "Следующие приглашенные были оповещены";
"The following attendees(s) were added" = "Следующие люди были добавлены в список приглашенных";
"The following attendees(s) were removed" = "Следующие люди были исключены из списка приглашенных";
/* IMIP messages */
"calendar_label" = "Календарь:";
"startDate_label" = "Начало:";
"endDate_label" = "Конец:";
"calendar_label" = "Календарь";
"startDate_label" = "Начало";
"endDate_label" = "Конец";
"due_label" = "Должно быть готово к:";
"location_label" = "Местонахождение:";
"location_label" = "Местонахождение";
"summary_label" = "Краткое содержание:";
"comment_label" = "Комментарий:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Приглашение на мероприятие: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(послал %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}пригласил вас на %{Summary}.\n\nНачало: %{StartDate}\nОкончание: %{EndDate}\nОписание: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} пригласил вас на %{Summary}.\n\Начало: %{StartDate} в %{StartTime}\nКонец: %{EndDate} в %{EndTime}\nОписание: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Мероприятие отменено: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}отменил эту встречу: %{Summary}.\n\nНачало: %{StartDate}\nОкончание: %{EndDate}\nОписание: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} отменил это мероприятие: %{Summary}.\n\nНачало: %{StartDate} в %{StartTime}\nКонец: %{EndDate} в %{EndTime}\nОписание: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Встреча \"%{Summary}\" назначенная на %{OldStartDate} была изменена";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Конфиденциальная задача)";
= "в мероприятии \"%{Summary}\" изменены следующие параметры:";
"Please accept or decline those changes."
= "Пожалуйста подтвердите или отмените эти изменения.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Принятое приглашение: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Отклонённое приглашение: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Конфиденциальная задача)";
= "%{Attendee} %{SentByText}делегировал приглашение %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}не определился с желанием участвовать в запланированном мероприятии.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Нет доступа к ресурсу: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Достигнуто предельное число заявок (%{NumberOfSimultaneousBookings}) на ресурс \"%{Cn} %{SystemEmail}\". Конкурирующее событие называется \"%{EventTitle}\", оно начинается %{StartDate}.";
@@ -157,12 +157,6 @@ 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;
+35 -71
View File
@@ -84,6 +84,7 @@
#import "SOGoAppointmentFolders.h"
#import "SOGoFreeBusyObject.h"
#import "SOGoTaskObject.h"
#import "SOGoWebAppointmentFolder.h"
#import "SOGoAppointmentFolder.h"
@@ -432,61 +433,6 @@ 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.
//
@@ -1063,7 +1009,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
{
if ([dateRange containsDate: [component startDate]])
{
// We must pass nill to :container here in order to avoid re-entrancy issues.
// We must pass nil to :container here in order to avoid re-entrancy issues.
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
}
@@ -1080,15 +1026,20 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
{
// The recurrence id of the exception is outside the date range;
// simply add the exception to the records array.
// We must pass nill to :container here in order to avoid re-entrancy issues.
// We must pass nil to :container here in order to avoid re-entrancy issues.
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
newRecordRange = [NGCalendarDateRange
calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"]
endDate: [newRecord objectForKey: @"endDate"]];
if ([dateRange doesIntersectWithDateRange: newRecordRange])
if ([newRecord objectForKey: @"startDate"] && [newRecord objectForKey: @"endDate"]) {
newRecordRange = [NGCalendarDateRange
calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"]
endDate: [newRecord objectForKey: @"endDate"]];
if ([dateRange doesIntersectWithDateRange: newRecordRange])
[ma addObject: newRecord];
else
else
newRecord = nil;
} else {
[self warnWithFormat: @"Recurrence %@ without dtstart or dtend. Ignoring", recurrenceId];
newRecord = nil;
}
}
if (newRecord)
@@ -2387,7 +2338,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
request = [context request];
if (!([request isIPhone] || [request isICal4]))
{
gdRT = [self groupDavResourceType];
gdRT = (NSArray *)[self groupDavResourceType];
gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
XMLNS_GROUPDAV, nil];
[colType addObject: gdVEventCol];
@@ -2980,7 +2931,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]))
@@ -3139,19 +3093,29 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
NSMutableString *content;
NSString *uid;
// We first look if there's an event with the same UID in our calendar. If not,
// let's reuse what is in the event, otherwise generate a new GUID and use it.
// We first look if the event has any / or + in its UID. If that's the case
// we generate a new UID based on a GUID
uid = [event uid];
object = [self lookupName: uid
inContext: context
acquire: NO];
if (object && ![object isKindOfClass: [NSException class]])
if ([uid rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"+/"]].location != NSNotFound)
{
uid = [self globallyUniqueObjectId];
[event setUid: uid];
}
else
{
// We also look if there's an event with the same UID in our calendar. If not,
// let's reuse what is in the event, otherwise generate a new GUID and use it.
object = [self lookupName: uid
inContext: context
acquire: NO];
if (object && ![object isKindOfClass: [NSException class]])
{
uid = [self globallyUniqueObjectId];
[event setUid: uid];
}
}
object = [SOGoAppointmentObject objectWithName: uid
inContainer: self];
+69 -44
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2007-2014 Inverse inc.
Copyright (C) 2007-2015 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOGo
@@ -45,6 +45,7 @@
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/iCalToDo.h>
#import <NGCards/NSString+NGCards.h>
#import <SOGo/SOGoConstants.h>
@@ -317,7 +318,7 @@
enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
currentUID = [currentAttendee uidInContext: context];
if (currentUID)
[self _removeEventFromUID: currentUID
owner: owner
@@ -398,7 +399,7 @@
enumerator = [updateAttendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
currentUID = [currentAttendee uidInContext: context];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID
@@ -438,7 +439,7 @@
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
currentUID = [currentAttendee uidInContext: context];
if (currentUID)
{
@@ -520,7 +521,7 @@
enumerator = [theAttendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
currentUID = [currentAttendee uidInContext: context];
if (currentUID)
{
[attendees addObject: currentUID];
@@ -635,7 +636,7 @@
for (i = 0; i < [theAttendees count]; i++)
{
currentAttendee = [theAttendees objectAtIndex: i];
if ([[currentAttendee uid] isEqualToString: currentUID])
if ([[currentAttendee uidInContext: context] isEqualToString: currentUID])
break;
else
currentAttendee = nil;
@@ -713,7 +714,7 @@
enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
currentUID = [currentAttendee uidInContext: context];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID
@@ -747,11 +748,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
e = [events objectAtIndex: i];
if ([e recurrenceId])
for (j = 0; j < [theAttendees count]; j++)
if (shouldAdd)
for (j = 0; j < [theAttendees count]; j++) {
if (shouldAdd) {
[e addToAttendees: [theAttendees objectAtIndex: j]];
else
}
else {
[e removeFromAttendees: [theAttendees objectAtIndex: j]];
}
}
}
}
@@ -838,7 +842,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
enumerator = [updatedAttendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
currentUID = [currentAttendee uidInContext: context];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID
@@ -1037,7 +1041,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([delegateEmail length])
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else
otherDelegate = NO;
otherDelegate = nil;
/* we handle the addition/deletion of delegate users */
addDelegate = NO;
@@ -1075,7 +1079,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([delegateEmail length])
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else
otherDelegate = NO;
otherDelegate = nil;
}
}
if (addDelegate)
@@ -1215,7 +1219,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
[delegates addObject: otherDelegate];
delegatedUID = [otherDelegate uid];
delegatedUID = [otherDelegate uidInContext: context];
if (delegatedUID)
// Delegate attendee is a local user; remove event from their calendar
[self _removeEventFromUID: delegatedUID
@@ -1232,7 +1236,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([delegateEmail length])
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else
otherDelegate = NO;
otherDelegate = nil;
}
[self sendEMailUsingTemplateNamed: @"Deletion"
@@ -1244,7 +1248,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if (addDelegate)
{
delegatedUID = [delegate uid];
delegatedUID = [delegate uidInContext: context];
delegates = [NSArray arrayWithObject: delegate];
[event addToAttendees: delegate];
@@ -1271,11 +1275,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[self sendResponseToOrganizer: event
from: ownerUser];
organizerUID = [[event organizer] uid];
organizerUID = [[event organizer] uidInContext: context];
// Event is an exception to a recurring event; retrieve organizer from master event
if (!organizerUID)
organizerUID = [[(iCalEntityObject*)[[event parent] firstChildWithTag: [self componentTag]] organizer] uid];
organizerUID = [[(iCalEntityObject*)[[event parent] firstChildWithTag: [self componentTag]] organizer] uidInContext: context];
if (organizerUID)
// Update the attendee in organizer's calendar.
@@ -1302,7 +1306,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
for (i = 0; i < [attendees count]; i++)
{
att = [attendees objectAtIndex: i];
uid = [att uid];
uid = [att uidInContext: context];
if (uid && att != attendee && ![uid isEqualToString: delegatedUID])
[self _updateAttendee: attendee
withDelegate: delegate
@@ -1461,7 +1465,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
if (delegate && ![[delegate email] isEqualToString: [attendee delegatedTo]])
{
delegatedUid = [delegate uid];
delegatedUid = [delegate uidInContext: context];
if (delegatedUid)
delegatedUser = [SOGoUser userWithLogin: delegatedUid];
if (delegatedUser != nil && [event userIsOrganizer: delegatedUser])
@@ -1831,7 +1835,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// We now make sure that the organizer, if managed by SOGo, is using
// its default email when creating events and inviting attendees.
uid = [[SOGoUserManager sharedUserManager] getUIDForEmail: [[event organizer] rfc822Email]];
uid = [[event organizer] uidInContext: context];
if (uid)
{
NSDictionary *defaultIdentity;
@@ -1990,7 +1994,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([container resourceNameForEventUID: eventUID])
{
return [NSException exceptionWithHTTPStatus: 403
reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]];
reason: [NSString stringWithFormat: @"Event UID already in use. (%@)", eventUID]];
}
//
@@ -2131,32 +2135,23 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
if ([[newEvent attendees] count] || [[oldEvent attendees] count])
{
NSString *uid;
BOOL userIsOrganizer;
// newEvent might be nil here, if we're deleting a RECURRENCE-ID with attendees
// If that's the case, we use the oldEvent for now just to obtain the organizer
// and we'll swap it back to nil once we're done.
if (!newEvent)
newEvent = oldEvent;
// We fetch the organizer's uid. Sometimes, the recurrence-id will
// have it, sometimes not. If it doesn't, we fetch it from the master event.
uid = [[newEvent organizer] uid];
if (!uid && !recurrenceId)
uid = [[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid];
// If that's the case, we use the oldEvent to obtain the organizer
if (newEvent)
userIsOrganizer = [newEvent userIsOrganizer: ownerUser];
else
userIsOrganizer = [oldEvent userIsOrganizer: ownerUser];
// With Thunderbird 10, if you create a recurring event with an exception
// occurence, and invite someone, the PUT will have the organizer in the
// recurrence-id and not in the master event. We must fix this, otherwise
// SOGo will break.
if (!recurrenceId && ![[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid])
if (!recurrenceId && ![[[[[newEvent parent] events] objectAtIndex: 0] organizer] uidInContext: context])
[[[[newEvent parent] events] objectAtIndex: 0] setOrganizer: [newEvent organizer]];
if (newEvent == oldEvent)
newEvent = nil;
if (uid && [uid caseInsensitiveCompare: owner] == NSOrderedSame)
if (userIsOrganizer)
{
// A RECCURENCE-ID was removed
if (!newEvent && oldEvent)
@@ -2176,13 +2171,43 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
iCalPerson *attendee, *delegate;
NSString *delegateEmail;
#if 1
attendee = [newEvent userAsAttendee: [SOGoUser userWithLogin: owner]];
#else
attendee = [oldEvent userAsAttendee: [SOGoUser userWithLogin: owner]];
if (!attendee)
attendee = [newEvent userAsAttendee: [SOGoUser userWithLogin: owner]];
else
{
// We must do an extra check here since Bob could have invited Alice
// using alice@example.com but she would have accepted with ATTENDEE set
// to sexy@example.com. That would duplicate the ATTENDEE and set the
// participation status to ACCEPTED for sexy@example.com but leave it
// to NEEDS-ACTION to alice@example. This can happen in Mozilla Thunderbird/Lightning
// when a user with multiple identities accepts an event invitation to one
// of its identity (which is different than the email address associated with
// the mail account) prior doing a calendar refresh.
NSMutableArray *attendees;
iCalPerson *participant;
attendees = [NSMutableArray arrayWithArray: [newEvent attendeesWithoutUser: [SOGoUser userWithLogin: owner]]];
participant = [newEvent participantForUser: [SOGoUser userWithLogin: owner]
attendee: attendee];
[attendee setPartStat: [participant partStat]];
[attendee setDelegatedFrom: [participant delegatedFrom]];
[attendee setDelegatedTo: [participant delegatedTo]];
[attendees addObject: attendee];
[newEvent setAttendees: attendees];
}
#endif
// We first check of the sequences are alright. We don't accept attendees
// accepting "old" invitations. If that's the case, we return a 403
if ([[newEvent sequence] intValue] < [[oldEvent sequence] intValue])
return [NSException exceptionWithHTTPStatus:403
return [NSException exceptionWithHTTPStatus: 403
reason: @"sequences don't match"];
// Remove the RSVP attribute, as an action from the attendee
@@ -140,6 +140,14 @@
return aclManager;
}
- (NSException *) changeParticipationStatus: (NSString *) newPartStat
withDelegate: (iCalPerson *) delegate
alarm: (iCalAlarm *) alarm
{
// Required for protocol <SOGoComponentOccurence>
return nil;
}
- (id) init
{
if ((self = [super init]))
@@ -171,7 +179,7 @@
- (Class *) parsingClass
{
return [iCalCalendar class];
return (Class *)[iCalCalendar class];
}
- (NSString *) davContentType
Loaded 100 of 619 files, more files were not shown because too many files have changed in this diff. Show more