diff --git a/app/storage/riak_storage.py b/app/storage/riak_storage.py index 793f256..4b7b48f 100644 --- a/app/storage/riak_storage.py +++ b/app/storage/riak_storage.py @@ -1,368 +1,368 @@ # -*- coding: utf-8 -*- # # Copyright 2015 Kolab Systems AG (http://www.kolabsys.com) # # Thomas Bruederli # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # import logging, datetime, urllib, urlparse from riak import RiakClient from riak.mapreduce import RiakKeyFilter, RiakMapReduce from dateutil.parser import parse as parse_date from flask import current_app from . import AbstractStorage conf = current_app.config log = logging.getLogger('storage') class RiakStorage(AbstractStorage): bucket_types = { 'users': 'egara-lww', 'users-current': 'egara-unique', 'imap-events': 'egara-lww', 'imap-folders': 'egara-lww', 'imap-folders-current': 'egara-unique', 'imap-message-timeline': 'egara-lww' } def __init__(self, *args, **kw): riak_host = 'localhost' riak_port = 8098 self.client = RiakClient( protocol='http', host=conf['STORAGE'].get('riak_host', riak_host), http_port=conf['STORAGE'].get('riak_port', riak_port) ) self.client.set_decoder('application/octet-stream', self._decode_binary) def _decode_binary(self, data): return str(data).encode("utf-8") def _get_bucket(self, bucketname): _type = self.bucket_types.get(bucketname, None) if _type: return self.client.bucket_type(_type).bucket(bucketname) return None def get(self, key, index, doctype=None, fields=None, **kw): """ Standard API for accessing key/value storage """ result = None log.debug("Riak get key %r from %r", key, index) try: bucket = self._get_bucket(index) res = bucket.get(key) if res and res.data: result = res.data except Exception, e: log.warning("Riak exception: %r", e) result = None return result def set(self, key, value, index, doctype=None, **kw): """ Standard API for writing to key/value storage """ return False def select(self, query, index, doctype=None, fields=None, sortby=None, limit=None, **kw): """ Standard API for querying storage """ result = None try: pass except Exception, e: log.warning("Riak exception: %r", e) result = None return result def _get_keyfilter(self, index, starts_with=None, ends_with=None, sortby=None, limit=None): """ Helper function to execute a key filter query """ results = None fs = None fe = None if starts_with is not None: fs = RiakKeyFilter().starts_with(starts_with) if ends_with is not None: fe = RiakKeyFilter().ends_with(ends_with) if fs and fe: keyfilter = fs & fe else: keyfilter = fs or fe return self._mapreduce_keyfilter(index, keyfilter, sortby, limit) def _mapreduce_keyfilter(self, index, keyfilter, sortby=None, limit=None): """ Helper function to execute a map-reduce query using the given key filter """ results = None log.debug("Riak query %r with key filter %r", index, keyfilter) mapred = RiakMapReduce(self.client) mapred.add_bucket(self._get_bucket(index)) mapred.add_key_filters(keyfilter) # custom Riak.mapValuesJson() function that also adds the entry key to the data structure mapred.map(""" function(value, keyData, arg) { if (value.not_found) { return [value]; } var _data, data = value["values"][0]["data"]; if (Riak.getClassName(data) !== "Array") { _data = JSON.parse(data); _data["_key"] = value.key; return [_data]; } else { return data } } """) if sortby is not None: comp = '<' if limit is not None and limit < 0 else '>' mapred.reduce_sort('function(a,b){ return (a.%s || 0) %s (b.%s || 0) ? 1 : 0; }' % (sortby, comp, sortby)) if limit is not None: mapred.reduce_limit(abs(limit)) try: results = mapred.run() except Exception, e: log.warning("Riak MapReduce exception: %r", e) results = None return results def get_user(self, id=None, username=None): """ API for resolving usernames and reading user info """ # search by ID using a key filter if id is not None: results = self._get_keyfilter('users', starts_with=id + '::', limit=1) if results and len(results) > 0: return results[0] elif username is not None: user = self.get(username, 'users-current') if user is not None: return user # TODO: query 'users' bucket with an ends_with key filter # TODO: add a very short-term cache for lookups by ID return None def get_folder(self, mailbox=None, user=None): """ API for finding IMAP folders and their unique identifiers """ folder_id = self.get(mailbox, 'imap-folders-current') if folder_id is not None: return dict(uri=mailbox, id=folder_id) return None def get_events(self, objuid, mailbox, msguid, limit=None): """ API for querying event notifications """ # 1. get timeline entries for current folder folder = self.get_folder(mailbox) if folder is None: log.info("Folder %r not found in storage", mailbox) return None; object_event_keys = self._get_timeline_keys(objuid, folder['id']) # sanity check with msguid if msguid is not None: key_prefix = 'message::%s::%s' % (folder['id'], str(msguid)) if len([k for k in object_event_keys if k.startswith(key_prefix)]) == 0: log.warning("Sanity check failed: requested msguid %r not in timeline keys %r", msguid, object_event_keys) # TODO: abort? # 3. read each corresponding entry from imap-events filters = None for key in object_event_keys: f = RiakKeyFilter().starts_with(key) if filters is None: filters = f else: filters |= f log.debug("Querying imap-events for keys %r", object_event_keys) if filters is not None: # TODO: query directly using key? - results = self._mapreduce_keyfilter('imap-events', filters, sortby='timestamp', limit=limit) + results = self._mapreduce_keyfilter('imap-events', filters, sortby='timestamp_utc', limit=limit) return [self._transform_result(x, 'imap-events') for x in results if x.has_key('event') and not x['event'] == 'MessageExpunge'] \ if results is not None else results return None def _get_timeline_keys(self, objuid, folder_id, length=3): """ Helper method to fetch timeline keys recursively following moves accross folders """ object_event_keys = [] results = self._get_keyfilter('imap-message-timeline', starts_with='message::' + folder_id + '::', ends_with='::' + objuid) if not results or len(results) == 0: log.info("No timeline entry found for %r in folder %r", objuid, folder_id) return object_event_keys; for rec in results: key = '::'.join(rec['_key'].split('::', 4)[0:length]) object_event_keys.append(key) # follow moves and add more :: tuples to our list if rec.has_key('history') and isinstance(rec['history'], dict) and rec['history'].has_key('imap'): old_folder_id = rec['history']['imap'].get('previous_folder', None) if old_folder_id: object_event_keys += self._get_timeline_keys(objuid, old_folder_id, length) return object_event_keys def get_revision(self, objuid, mailbox, msguid, rev): """ API to get a certain revision of a stored object """ # resolve mailbox first folder = self.get_folder(mailbox) if folder is None: log.info("Folder %r not found in storage", mailbox) return None; # expand revision into the ISO timestamp format try: ts = datetime.datetime.strptime(str(rev), "%Y%m%d%H%M%S%f") timestamp = ts.strftime("%Y-%m-%dT%H:%M:%S.%f")[0:23] except Exception, e: log.warning("Invalid revision %r for object %r: %r", rev, objuid, e) return None # query message-timeline entries starting at peak with current folder (aka mailbox) object_event_keys = self._get_timeline_keys(objuid, folder['id'], length=4) # get the one key matching the revision timestamp keys = [k for k in object_event_keys if '::' + timestamp in k] log.debug("Get revision entry %r from candidates %r", timestamp, object_event_keys) if len(keys) == 1: result = self.get(keys[0], 'imap-events') if result is not None: return self._transform_result(result, 'imap-events') else: log.info("Revision timestamp %r doesn't match a single key from: %r", timestamp, object_event_keys) return None def get_message_data(self, rec): """ Getter for the full IMAP message payload for the given event record as previously fetched with get_events() or get_revision() """ return rec.get('message', None) def _transform_result(self, result, index): """ Turn an imap-event record into a dict to match the storage API """ result['_index'] = index # derrive (numeric) revision from timestamp - if result.has_key('timestamp') and result.get('event','') in ['MessageAppend','MessageMove']: + if result.has_key('timestamp_utc') and result.get('event','') in ['MessageAppend','MessageMove']: try: - ts = parse_date(result['timestamp']) + ts = parse_date(result['timestamp_utc']) result['revision'] = ts.strftime("%Y%m%d%H%M%S%f")[0:17] except: pass # extract folder name from uri if result.has_key('uri') and not result.has_key('mailbox'): uri = self._parse_imap_uri(result['uri']) username = uri['user'] domain = uri['domain'] folder_name = uri['path'] folder_path = uri['path'] imap_delimiter = '/' if not username == None: if folder_name == "INBOX": folder_path = imap_delimiter.join(['user', '%s@%s' % (username, domain)]) else: folder_path = imap_delimiter.join(['user', username, '%s@%s' % (folder_name, domain)]) result['mailbox'] = folder_path return result def _parse_imap_uri(self, uri): """ Split the given URI string into its components """ split_uri = urlparse.urlsplit(uri) if len(split_uri.netloc.split('@')) == 3: (username, domain, server) = split_uri.netloc.split('@') elif len(split_uri.netloc.split('@')) == 2: (username, server) = split_uri.netloc.split('@') domain = None elif len(split_uri.netloc.split('@')) == 1: username = None domain = None server = split_uri.netloc result = dict(user=username, domain=domain, host=server) # First, .path == '/Calendar/Personal%20Calendar;UIDVALIDITY=$x[/;UID=$y] # Take everything after the first slash, and omit any INBOX/ stuff. path_str = '/'.join([x for x in split_uri.path.split('/') if not x == 'INBOX'][1:]) path_arr = path_str.split(';') result['path'] = urllib.unquote(path_arr[0]) # parse the path/query parameters into a dict param = dict() for p in path_arr[1:]: if '=' in p: (key,val) = p.split('=', 2) result[key] = urllib.unquote(val) return result diff --git a/tests/insert_testdata.sh b/tests/insert_testdata.sh index e63650f..b2c8b57 100755 --- a/tests/insert_testdata.sh +++ b/tests/insert_testdata.sh @@ -1,446 +1,459 @@ #!/bin/sh RIAK_HOST='localhost' RIAK_PORT='10018' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-unique/buckets/users-current/keys/john.doe@example.org" \ -H 'Content-Type: application/json' \ -d '{ "dn": "uid=doe,ou=People,dc=example,dc=org", "cn": "Doe, John", "user": "john.doe@example.org", "id": "55475201-bdc211e4-881c96ef-f248ab46" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/users/keys/55475201-bdc211e4-881c96ef-f248ab46::2015-03-07T14:10:16.941541::john.doe@example.org" \ -H 'Content-Type: application/json' \ -d '{ "dn": "uid=doe,ou=People,dc=example,dc=org", "cn": "Doe, John", "user": "john.doe@example.org", "id": "55475201-bdc211e4-881c96ef-f248ab46" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-unique/buckets/imap-folders-current/keys/user%2Fjohn.doe%2FCalendar%40example.org" \ -H 'Content-Type: application/octet-stream' \ -d 'a5660caa-3165-4a84-bacd-ef4b58ef3663' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::3::2015-03-04T09:11:39.711Z" \ -H 'Content-Type: application/json' \ -d '{ "event": "MessageAppend", "vnd.cmu.envelope": "(...)", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "message": "MIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"=_2bf2e936dd4806dab1e774f4bf4cb5b5\"\r\nFrom: john.doe@example.org\r\nTo: john.doe@example.org\r\nX-Kolab-Type: application/x-vnd.kolab.event\r\nX-Kolab-Mime-Version: 3.0\r\nSubject: 6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271\r\n\r\n--=_2bf2e936dd4806dab1e774f4bf4cb5b5\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nThis is a Kolab Groupware object. To view this object you will need an emai=\r\nl client that understands the Kolab Groupware format. For a list of such em=\r\nail clients please visit http://www.kolab.org/\r\n\r\n\r\n--=_2bf2e936dd4806dab1e774f4bf4cb5b5\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: application/calendar+xml; charset=UTF-8;\r\n name=kolab.xml\r\nContent-Disposition: attachment;\r\n filename=kolab.xml;\r\n size=1950\r\n\r\n\r\n\r\nRoundcube-libkolab-1.1 Libkolabxml-1.22.03.1.06EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA2712015-03-02T23:13:57Z2015-03-02T23:52:22Z2PUBLIC/kolab.org/Europe/Berlin2015-03-03T12:00:00/kolab.org/Europe/Berlin2015-03-03T14:00:00Todays Egara Testingkolab34.example.orgDoe, Johnmailto:%3Cjohn.doe%40example.org%3E\r\n\r\n\r\n\r\n--=_2bf2e936dd4806dab1e774f4bf4cb5b5--", "bodyStructure": "(...)", "service": "imap", "modseq": 2, - "timestamp": "2015-03-04T09:11:39.711Z", + "timestamp": "2015-03-04T09:11:39.711+00:00", + "timestamp_utc": "2015-03-04T09:11:39.711Z", "pid": 28071, "vnd.cmu.sessionId": "kolab34.example.org-28071-1425478299-1-14630748386624877725", "messages": 1, "uri": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960274/;UID=3", "uidset": "3", "messageSize": 2843, "uidnext": 4, "user": "john.doe@example.org", "vnd.cmu.unseenMessages": 1, "vnd.cmu.midset": [ "NIL" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_2bf2e936dd4806dab1e774f4bf4cb5b5\"", "Subject": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271", "From": "john.doe@example.org", "To": "john.doe@example.org", "X-Kolab-Type": "application/x-vnd.kolab.event" - } + }, + "groupware_uid": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::3::2015-03-04T09:11:39.711Z::6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{}' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::4::2015-03-04T21:56:46.465Z" \ -H 'Content-Type: application/json' \ -d '{ "event": "MessageAppend", "vnd.cmu.envelope": "(...)", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "message": "MIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"=_fc7d881734b2f8b6cf99458899d79664\"\r\nFrom: john.doe@example.org\r\nTo: john.doe@example.org\r\nX-Kolab-Type: application/x-vnd.kolab.event\r\nX-Kolab-Mime-Version: 3.0\r\nSubject: 6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271\r\n\r\n--=_fc7d881734b2f8b6cf99458899d79664\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nThis is a Kolab Groupware object. To view this object you will need an emai=\r\nl client that understands the Kolab Groupware format. For a list of such em=\r\nail clients please visit http://www.kolab.org/\r\n\r\n\r\n--=_fc7d881734b2f8b6cf99458899d79664\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: application/calendar+xml; charset=UTF-8;\r\n name=kolab.xml\r\nContent-Disposition: attachment;\r\n filename=kolab.xml;\r\n size=1950\r\n\r\n\r\n\r\nRoundcube-libkolab-1.1 Libkolabxml-1.22.03.1.06EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA2712015-03-02T23:13:57Z2015-03-05T02:56:46Z3PUBLIC/kolab.org/Europe/Berlin2015-03-05T13:00:00/kolab.org/Europe/Berlin2015-03-05T15:00:00Todays Egara Testingkolab34.example.orgDoe, Johnmailto:%3Cjohn.doe%40example.org%3E\r\n\r\n\r\n\r\n--=_fc7d881734b2f8b6cf99458899d79664--", "bodyStructure": "(...)", "service": "imap", "modseq": 9, - "timestamp": "2015-03-04T21:56:46.465Z", + "timestamp": "2015-03-04T21:56:46.465+00:00", + "timestamp_utc": "2015-03-04T21:56:46.465Z", "pid": 981, "vnd.cmu.sessionId": "kolab34.example.org-981-1425524206-1-5416454962879660084", "messages": 2, "uri": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960330/;UID=4", "uidset": "4", "messageSize": 2910, "uidnext": 5, "user": "john.doe@example.org", "vnd.cmu.unseenMessages": 2, "vnd.cmu.midset": [ "NIL" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_fc7d881734b2f8b6cf99458899d79664\"", "Subject": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271", "From": "john.doe@example.org", "To": "john.doe@example.org", "X-Kolab-Mime-Version": "3.0", "X-Kolab-Type": "application/x-vnd.kolab.event" - } + }, + "groupware_uid": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::4::2015-03-04T21:56:46.465Z::6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{}' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::3::2015-03-04T21:56:46.500Z" \ -H 'Content-Type: application/json' \ -d '{ "event": "MessageTrash", "uidset": "3", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "service": "imap", "modseq": 10, - "timestamp": "2015-03-04T21:56:46.500Z", + "timestamp": "2015-03-04T21:56:46.500+00:00", + "timestamp_utc": "2015-03-04T21:56:46.500Z", "pid": 981, "vnd.cmu.sessionId": "kolab34.example.org-981-1425524206-1-5416454962879660084", "messages": 2, "uri": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960330", "uidnext": 5, "user": "john.doe@example.org", "vnd.cmu.unseenMessages": 2, - "vnd.cmu.midset": [ "NIL" ], - "headers": { - "Subject": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271", - "From": "john.doe@example.org", - "To": "john.doe@example.org", - "X-Kolab-Mime-Version": "3.0", - "X-Kolab-Type": "application/x-vnd.kolab.event" - } + "vnd.cmu.midset": [ "NIL" ] }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::21::2015-03-17T15:58:43.016000Z" \ -H 'Content-Type: application/json' \ -d '{ "message": "MIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"=_c54c7baa744029b81ae04981de13c8b5\"\r\nFrom: john.doe@example.org\r\nTo: john.doe@example.org\r\nX-Kolab-Type: application/x-vnd.kolab.event\r\nX-Kolab-Mime-Version: 3.0\r\nSubject: 5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271\r\n\r\n--=_c54c7baa744029b81ae04981de13c8b5\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nThis is a Kolab Groupware object. To view this object you will need an emai=\r\nl client that understands the Kolab Groupware format. For a list of such em=\r\nail clients please visit http://www.kolab.org/\r\n\r\n\r\n--=_c54c7baa744029b81ae04981de13c8b5\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: application/calendar+xml; charset=UTF-8;\r\n name=kolab.xml\r\nContent-Disposition: attachment;\r\n filename=kolab.xml;\r\n size=1960\r\n\r\n\r\n\r\nRoundcube-libkolab-1.1 Libkolabxml-1.22.03.1.05A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA2712015-03-17T15:58:42Z2015-03-17T15:58:42Z0PUBLIC/kolab.org/Europe/Berlin2015-03-19T11:00:00/kolab.org/Europe/Berlin2015-03-19T13:00:00Thursday TestThis is a test event for EgaraDoe, Johnmailto:%3Cjohn.doe%40example.org%3E\r\n\r\n\r\n\r\n--=_c54c7baa744029b81ae04981de13c8b5--\r\n", "bodyStructure": "((\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"QUOTED-PRINTABLE\" 206 4 NIL NIL NIL NIL)(\"APPLICATION\" \"CALENDAR+XML\" (\"CHARSET\" \"UTF-8\" \"NAME\" \"kolab.xml\") NIL NIL \"8BIT\" 1960 NIL (\"ATTACHMENT\" (\"FILENAME\" \"kolab.xml\" \"SIZE\" \"1960\")) NIL NIL) \"MIXED\" (\"BOUNDARY\" \"=_c54c7baa744029b81ae04981de13c8b5\") NIL NIL NIL)", "event": "MessageAppend", "flags": [ "\\Recent" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_c54c7baa744029b81ae04981de13c8b5\"", "Date": "Tue, 17 Mar 2015 15:58:42 +0000", "From": "john.doe@example.org", "MIME-Version": "1.0", "Subject": "5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271", "To": "john.doe@example.org", "X-Kolab-Mime-Version": "3.0", "X-Kolab-Type": "application/x-vnd.kolab.event" }, "messageSize": 2920, "messages": 4, "modseq": 57, "pid": 1579, "service": "imap", - "timestamp": "2015-03-17T15:58:43.016000Z", + "timestamp": "2015-03-17T15:58:43.016+00:00", + "timestamp_utc": "2015-03-17T15:58:43.016000Z", "uidnext": 22, "uri": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960330/;UID=21", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.envelope": "(\"Tue, 17 Mar 2015 15:58:42 +0000\" \"5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271\" ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) NIL NIL NIL NIL)", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-1579-1426607922-1-10050682032034970758", - "vnd.cmu.unseenMessages": 4 + "vnd.cmu.unseenMessages": 4, + "groupware_uid": "5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::21::2015-03-17T15:58:43.016000Z::5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{}' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::22::2015-03-17T17:17:37.514000Z" \ -H 'Content-Type: application/json' \ -d '{ "message": "MIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"=_e8dedd96d48a3bdb20e76ee77e234363\"\r\nFrom: john.doe@example.org\r\nTo: john.doe@example.org\r\nX-Kolab-Type: application/x-vnd.kolab.event\r\nX-Kolab-Mime-Version: 3.0\r\nSubject: 5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271\r\n\r\n--=_e8dedd96d48a3bdb20e76ee77e234363\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nThis is a Kolab Groupware object. To view this object you will need an emai=\r\nl client that understands the Kolab Groupware format. For a list of such em=\r\nail clients please visit http://www.kolab.org/\r\n\r\n\r\n--=_e8dedd96d48a3bdb20e76ee77e234363\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: application/calendar+xml; charset=UTF-8;\r\n name=kolab.xml\r\nContent-Disposition: attachment;\r\n filename=kolab.xml;\r\n size=2123\r\n\r\n\r\n\r\nRoundcube-libkolab-1.1 Libkolabxml-1.22.03.1.05A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA2712015-03-17T15:58:42Z2015-03-17T17:17:37Z1PUBLIC/kolab.org/Europe/Berlin2015-03-19T10:00:00/kolab.org/Europe/Berlin2015-03-19T11:00:00Thursday TestThis is a test event for EgaraCONFIRMEDSomewhere elseDoe, Johnmailto:%3Cjohn.doe%40example.org%3E\r\n\r\n\r\n\r\n--=_e8dedd96d48a3bdb20e76ee77e234363--\r\n", "bodyStructure": "((\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"QUOTED-PRINTABLE\" 206 4 NIL NIL NIL NIL)(\"APPLICATION\" \"CALENDAR+XML\" (\"CHARSET\" \"UTF-8\" \"NAME\" \"kolab.xml\") NIL NIL \"8BIT\" 2123 NIL (\"ATTACHMENT\" (\"FILENAME\" \"kolab.xml\" \"SIZE\" \"2123\")) NIL NIL) \"MIXED\" (\"BOUNDARY\" \"=_e8dedd96d48a3bdb20e76ee77e234363\") NIL NIL NIL)", "event": "MessageAppend", "flags": [ "\\Recent" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_e8dedd96d48a3bdb20e76ee77e234363\"", "Date": "Tue, 17 Mar 2015 17:17:37 +0000", "From": "john.doe@example.org", "MIME-Version": "1.0", "Subject": "5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271", "To": "john.doe@example.org", "X-Kolab-Mime-Version": "3.0", "X-Kolab-Type": "application/x-vnd.kolab.event" }, "messageSize": 3083, "messages": 5, "modseq": 58, "pid": 7493, "service": "imap", - "timestamp": "2015-03-17T17:17:37.514000Z", + "timestamp": "2015-03-17T17:17:37.514+00:00", + "timestamp_utc": "2015-03-17T17:17:37.514000Z", "uidnext": 23, "uidset": "22", "uri": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960330/;UID=22", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.envelope": "(\"Tue, 17 Mar 2015 17:17:37 +0000\" \"5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271\" ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) NIL NIL NIL NIL)", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-7493-1426612657-1-6878020795100368520", - "vnd.cmu.unseenMessages": 5 + "vnd.cmu.unseenMessages": 5, + "groupware_uid": "5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::22::2015-03-17T17:17:37.514000Z::5A637BE7895D785671E1732356E65CC8-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{}' curl -XPUT "$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::21::2015-03-17T17:17:37.562000Z" \ -H 'Content-Type: application/json' \ -d '{ "event": "MessageExpunge", "messages": 4, "modseq": 60, "pid": 7493, "service": "imap", - "timestamp": "2015-03-17T17:17:37.562000Z", + "timestamp": "2015-03-17T17:17:37.562+00:00", + "timestamp_utc": "2015-03-17T17:17:37.562000Z", "uidnext": 23, "uidset": "21", "uri": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960330", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-7493-1426612657-1-6878020795100368520", "vnd.cmu.unseenMessages": 4 }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::a5660caa-3165-4a84-bacd-ef4b58ef3663::21::2015-03-17T17:17:37.554000Z" \ -H 'Content-Type: application/json' \ -d '{ "event": "MessageTrash", "messages": 5, "modseq": 59, "pid": 7493, "service": "imap", - "timestamp": "2015-03-17T17:17:37.554000Z", + "timestamp": "2015-03-17T17:17:37.554+00:00", + "timestamp_utc": "2015-03-17T17:17:37.554000Z", "uidnext": 23, "uidset": "21", "uri": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960330", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-7493-1426612657-1-6878020795100368520", "vnd.cmu.unseenMessages": 5 }' # move event to folder Testing curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-unique/buckets/imap-folders-current/keys/user%2Fjohn.doe%2FTesting%40example.org" \ -H 'Content-Type: application/octet-stream' \ -d 'fe0137f5-6828-474f-9cf6-fdf135123679' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::1::2015-03-18T04:13:31.158000Z" \ -H 'Content-Type: application/json' \ -d '{ "event": "MessageMove", "messages": 1, "modseq": 3, "oldMailboxID": "imap://john.doe@example.org@kolab34.example.org/Calendar;UIDVALIDITY=1424960330", "pid": 12489, "service": "imap", - "timestamp": "2015-03-18T04:13:31.158000Z", + "timestamp": "2015-03-18T04:13:31.158+00:00", + "timestamp_utc": "2015-03-18T04:13:31.158000Z", "uidnext": 2, "uidset": "1", "uri": "imap://john.doe@example.org@kolab34.example.org/Testing;UIDVALIDITY=1426651436", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.midset": [ "NIL" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_8e9630d5eb4cee74209f3d6f1736c0b5\"", "Date": "Wed, 18 Mar 2015 14:13:07 +0100", "From": "john.doe@example.org", "MIME-Version": "1.0", "Subject": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271", "To": "john.doe@example.org", "X-Kolab-Mime-Version": "3.0", "X-Kolab-Type": "application/x-vnd.kolab.event" }, "vnd.cmu.oldUidset": "4", "vnd.cmu.sessionId": "kolab34.example.org-12489-1426652010-1-1342732898036616806", - "vnd.cmu.unseenMessages": 1 + "vnd.cmu.unseenMessages": 1, + "groupware_uid": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::1::2015-03-18T04:13:31.158000Z::6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{ "history": { "imap": { "previous_folder": "a5660caa-3165-4a84-bacd-ef4b58ef3663", "previous_id": "4" } } }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::2::2015-03-18T04:13:31.454000Z" \ -H 'Content-Type: application/json' \ -d '{ "message": "MIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"=_8e9630d5eb4cee74209f3d6f1736c0b5\"\r\nFrom: john.doe@example.org\r\nTo: john.doe@example.org\r\nX-Kolab-Type: application/x-vnd.kolab.event\r\nX-Kolab-Mime-Version: 3.0\r\nSubject: 6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271\r\n\r\n--=_8e9630d5eb4cee74209f3d6f1736c0b5\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nThis is a Kolab Groupware object. To view this object you will need an emai=\r\nl client that understands the Kolab Groupware format. For a list of such em=\r\nail clients please visit http://www.kolab.org/\r\n\r\n\r\n--=_8e9630d5eb4cee74209f3d6f1736c0b5\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: application/calendar+xml; charset=UTF-8;\r\n name=kolab.xml\r\nContent-Disposition: attachment;\r\n filename=kolab.xml;\r\n size=1950\r\n\r\n\r\n\r\nRoundcube-libkolab-1.1 Libkolabxml-1.12.03.1.06EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA2712015-03-02T23:13:57Z2015-03-18T13:35:07Z13PUBLIC/kolab.org/Europe/Berlin2015-03-17T13:00:00/kolab.org/Europe/Berlin2015-03-17T15:00:00Todays Egara Testingkolab34.example.orgJohn Doemailto:%3Cjohn.doe%40example.org%3E\r\n\r\n\r\n\r\n--=_8e9630d5eb4cee74209f3d6f1736c0b5--\r\n", "bodyStructure": "(...)", "event": "MessageAppend", "flags": [ "\\Recent" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_8e9630d5eb4cee74209f3d6f1736c0b5\"", "Date": "Wed, 18 Mar 2015 14:35:07 +0100", "From": "john.doe@example.org", "MIME-Version": "1.0", "Subject": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271", "To": "john.doe@example.org", "X-Kolab-Mime-Version": "3.0", "X-Kolab-Type": "application/x-vnd.kolab.event" }, "messageSize": 2910, "messages": 2, "modseq": 4, "pid": 12489, "service": "imap", - "timestamp": "2015-03-18T04:13:31.454000Z", + "timestamp": "2015-03-18T04:13:31.454+00:00", + "timestamp_utc": "2015-03-18T04:13:31.454000Z", "uidnext": 3, "uidset": "2", "uri": "imap://john.doe@example.org@kolab34.example.org/Testing;UIDVALIDITY=1426651436/;UID=2", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.envelope": "(\"Wed, 18 Mar 2015 14:35:07 +0100\" \"6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271\" ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) NIL NIL NIL NIL)", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-12489-1426652010-1-1342732898036616806", - "vnd.cmu.unseenMessages": 2 + "vnd.cmu.unseenMessages": 2, + "groupware_uid": "6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::2::2015-03-18T04:13:31.454000Z::6EE0570E8CA21DDB67FC9ADE5EE38E7F-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{}' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::1::2015-03-18T04:13:31.467000Z" \ -H 'Content-Type: application/json' \ -d '{ "event": "MessageTrash", "messages": 2, "modseq": 5, "pid": 12489, "service": "imap", - "timestamp": "2015-03-18T04:13:31.467000Z", + "timestamp": "2015-03-18T04:13:31.467+00:00", + "timestamp_utc": "2015-03-18T04:13:31.467000Z", "uidnext": 3, "uidset": "1", "uri": "imap://john.doe@example.org@kolab34.example.org/Testing;UIDVALIDITY=1426651436", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-12489-1426652010-1-1342732898036616806", "vnd.cmu.unseenMessages": 2 }' # new event with attachment curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::3::2015-03-18T04:32:29.376000Z" \ -H 'Content-Type: application/json' \ -d '{ "message": "MIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"=_03f3c3e51790c8ff68ca5414a293ae51\"\r\nFrom: john.doe@example.org\r\nTo: john.doe@example.org\r\nX-Kolab-Type: application/x-vnd.kolab.event\r\nX-Kolab-Mime-Version: 3.0\r\nSubject: 390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271\r\n\r\n--=_03f3c3e51790c8ff68ca5414a293ae51\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nThis is a Kolab Groupware object. To view this object you will need an emai=\r\nl client that understands the Kolab Groupware format. For a list of such em=\r\nail clients please visit http://www.kolab.org/\r\n\r\n\r\n--=_03f3c3e51790c8ff68ca5414a293ae51\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: application/calendar+xml; charset=UTF-8;\r\n name=kolab.xml\r\nContent-Disposition: attachment;\r\n filename=kolab.xml;\r\n size=2195\r\n\r\n\r\n\r\nRoundcube-libkolab-1.1 Libkolabxml-1.12.03.1.0390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA2712015-03-18T13:54:05Z2015-03-18T13:54:05Z0PUBLIC/kolab.org/Europe/Berlin2015-03-20T16:00:00/kolab.org/Europe/Berlin2015-03-20T17:00:00Attachments TestJohn Doemailto:%3Cjohn.doe%40example.org%3Etext/plainattachment.txtcid:attachment.1426686845.2073.txt\r\n\r\n\r\n\r\n--=_03f3c3e51790c8ff68ca5414a293ae51\r\nContent-ID: \r\nContent-Transfer-Encoding: base64\r\nContent-Type: text/plain;\r\n name=attachment.txt\r\nContent-Disposition: attachment;\r\n filename=attachment.txt;\r\n size=37\r\n\r\nVGhpcyBpcyBhIHRleHQgYXR0YWNobWVudCAodmVyc2lvbiAxKQ==\r\n--=_03f3c3e51790c8ff68ca5414a293ae51--\r\n", "bodyStructure": "(...)", "event": "MessageAppend", "flags": [ "\\Recent" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_03f3c3e51790c8ff68ca5414a293ae51\"", "Date": "Wed, 18 Mar 2015 14:54:05 +0100", "From": "john.doe@example.org", "MIME-Version": "1.0", "Subject": "390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271", "To": "john.doe@example.org", "X-Kolab-Mime-Version": "3.0", "X-Kolab-Type": "application/x-vnd.kolab.event" }, "messageSize": 3450, "messages": 2, "modseq": 7, "pid": 12618, "service": "imap", - "timestamp": "2015-03-18T04:32:29.376000Z", + "timestamp": "2015-03-18T04:32:29.376+00:00", + "timestamp_utc": "2015-03-18T04:32:29.376000Z", "uidset": 3, "uidnext": 4, "uri": "imap://john.doe@example.org@kolab34.example.org/Testing;UIDVALIDITY=1426651436/;UID=3", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.envelope": "(\"Wed, 18 Mar 2015 14:54:05 +0100\" \"390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271\" ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) NIL NIL NIL NIL)", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-12618-1426653149-1-11265804361548763525", - "vnd.cmu.unseenMessages": 2 + "vnd.cmu.unseenMessages": 2, + "groupware_uid": "390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::3::2015-03-18T04:32:29.376000Z::390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{}' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-events/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::4::2015-03-18T04:36:34.162000Z" \ -H 'Content-Type: application/json' \ -d '{ "message": "MIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"=_531f398ebbeecbf523661f67827bea1e\"\r\nFrom: john.doe@example.org\r\nTo: john.doe@example.org\r\nX-Kolab-Type: application/x-vnd.kolab.event\r\nX-Kolab-Mime-Version: 3.0\r\nSubject: 390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271\r\n\r\n--=_531f398ebbeecbf523661f67827bea1e\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nThis is a Kolab Groupware object. To view this object you will need an emai=\r\nl client that understands the Kolab Groupware format. For a list of such em=\r\nail clients please visit http://www.kolab.org/\r\n\r\n\r\n--=_531f398ebbeecbf523661f67827bea1e\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: application/calendar+xml; charset=UTF-8;\r\n name=kolab.xml\r\nContent-Disposition: attachment;\r\n filename=kolab.xml;\r\n size=2195\r\n\r\n\r\n\r\nRoundcube-libkolab-1.1 Libkolabxml-1.12.03.1.0390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA2712015-03-18T13:54:05Z2015-03-18T13:58:09Z0PUBLIC/kolab.org/Europe/Berlin2015-03-20T16:00:00/kolab.org/Europe/Berlin2015-03-20T17:00:00Attachments TestJohn Doemailto:%3Cjohn.doe%40example.org%3Etext/plainattachment.txtcid:attachment.1426687089.9628.txt\r\n\r\n\r\n\r\n--=_531f398ebbeecbf523661f67827bea1e\r\nContent-ID: \r\nContent-Transfer-Encoding: base64\r\nContent-Type: text/plain;\r\n name=attachment.txt\r\nContent-Disposition: attachment;\r\n filename=attachment.txt;\r\n size=56\r\n\r\nVGhpcyBpcyBhIHRleHQgYXR0YWNobWVudCAodmVyc2lvbiAyKQp3aXRoIGEgc2Vjb25kIGxpbmU=\r\n--=_531f398ebbeecbf523661f67827bea1e--\r\n", "bodyStructure": "(...)", "event": "MessageAppend", "flags": [ "\\Recent" ], "headers": { "Content-Type": "multipart/mixed; boundary=\"=_531f398ebbeecbf523661f67827bea1e\"", "Date": "Wed, 18 Mar 2015 14:58:10 +0100", "From": "john.doe@example.org", "MIME-Version": "1.0", "Subject": "390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271", "To": "john.doe@example.org", "X-Kolab-Mime-Version": "3.0", "X-Kolab-Type": "application/x-vnd.kolab.event" }, "messageSize": 3474, "messages": 3, "modseq": 8, "pid": 12671, "service": "imap", - "timestamp": "2015-03-18T04:36:34.162000Z", + "timestamp": "2015-03-18T04:36:34.162+00:00", + "timestamp_utc": "2015-03-18T04:36:34.162000Z", "uidset": 4, "uidnext": 5, "uri": "imap://john.doe@example.org@kolab34.example.org/Testing;UIDVALIDITY=1426651436/;UID=4", "user": "john.doe@example.org", "user_id": "55475201-bdc211e4-881c96ef-f248ab46", "vnd.cmu.envelope": "(\"Wed, 18 Mar 2015 14:58:10 +0100\" \"390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271\" ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) ((NIL NIL \"john.doe\" \"example.org\")) NIL NIL NIL NIL)", "vnd.cmu.midset": [ "NIL" ], "vnd.cmu.sessionId": "kolab34.example.org-12671-1426653393-1-5448614388380127124", - "vnd.cmu.unseenMessages": 3 + "vnd.cmu.unseenMessages": 3, + "groupware_uid": "390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271" }' curl -XPUT "http://$RIAK_HOST:$RIAK_PORT/types/egara-lww/buckets/imap-message-timeline/keys/message::fe0137f5-6828-474f-9cf6-fdf135123679::4::2015-03-18T04:36:34.162000Z::390582E807A257686D51A6BF87F342E9-A4BF5BBB9FEAA271" \ -H 'Content-Type: application/json' \ -d '{}'