def test_success_conversion(store, icaltomapi, message): ical = open(os.path.join(dir_path, 'ics/success.ics'), 'rb').read() # As defined in namedprops.h DISPID_APPT_TS_REF = 0x25 NAMED_PROP_UID = MAPINAMEID(PSETID_Kopano_CalDav, MNID_ID, DISPID_APPT_TS_REF) NAMED_PROP_CATEGORY = MAPINAMEID(PS_PUBLIC_STRINGS, MNID_STRING, 'Keywords') properties = store.GetIDsFromNames([NAMED_PROP_UID, NAMED_PROP_CATEGORY], MAPI_CREATE) EXPECTED_MESSAGES = 1 UID = CHANGE_PROP_TYPE(properties[0], PT_BINARY) #0x85100102 CATEGORY = CHANGE_PROP_TYPE(properties[1], PT_MV_UNICODE) #0x850B101E START_DATE_PT_SYSTIME = 135547524000000000 END_DATE_PT_SYSTIME = 135547920000000000 prop_dic = { UID: [b'*****@*****.**'], PR_SUBJECT: [b'A dream holiday in the mountains'], CATEGORY: [['Holidays']], PR_SENDER_NAME_W: ['John Holidays'], PR_SENDER_EMAIL_ADDRESS_W: ['*****@*****.**'], PR_START_DATE: [FileTime(START_DATE_PT_SYSTIME)], PR_END_DATE: [FileTime(END_DATE_PT_SYSTIME)], } assert_item_count_from_ical(icaltomapi, ical, EXPECTED_MESSAGES) assert_properties_from_ical(icaltomapi, message, prop_dic)
def _arch_item( self ): # open archive store explicitly so we can handle otherwise silenced errors (MAPI errors in mail bodies for example) if self._architem is None: if self.stubbed: ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) PROP_STORE_ENTRYIDS = CHANGE_PROP_TYPE(ids[0], PT_MV_BINARY) PROP_ITEM_ENTRYIDS = CHANGE_PROP_TYPE(ids[1], PT_MV_BINARY) # support for multiple archives was a mistake, and is not and _should not_ be used. so we just pick nr 0. arch_storeid = HrGetOneProp(self.mapiobj, PROP_STORE_ENTRYIDS).Value[0] item_entryid = HrGetOneProp(self.mapiobj, PROP_ITEM_ENTRYIDS).Value[0] try: arch_store = self.server._store2(arch_storeid) except MAPIErrorUnconfigured: raise Error( 'could not open archive store (check SSL settings?)') self._architem = arch_store.OpenEntry(item_entryid, None, 0) else: self._architem = self.mapiobj return self._architem
def archive_store(self, store): ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) # XXX merge namedprops stuff PROP_STORE_ENTRYIDS = CHANGE_PROP_TYPE(ids[0], PT_MV_BINARY) PROP_ITEM_ENTRYIDS = CHANGE_PROP_TYPE(ids[1], PT_MV_BINARY) # XXX only for detaching atm if store is None: self.mapiobj.DeleteProps([PROP_STORE_ENTRYIDS, PROP_ITEM_ENTRYIDS]) self.mapiobj.SaveChanges(KEEP_OPEN_READWRITE)
def archive_store(self, store): # TODO merge namedprops stuff ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, MAPI_CREATE) PROP_STORE_ENTRYIDS = CHANGE_PROP_TYPE(ids[0], PT_MV_BINARY) PROP_ITEM_ENTRYIDS = CHANGE_PROP_TYPE(ids[1], PT_MV_BINARY) # TODO only for detaching atm if store is None: self.mapiobj.DeleteProps([PROP_STORE_ENTRYIDS, PROP_ITEM_ENTRYIDS]) _utils._save(self.mapiobj)
def test_round_trip_all_day_dstart_dend_style_not_date_incorrect(store, icaltomapi, mapitoical, message): ical = open(os.path.join(dir_path, 'ics/allday_dstart_dend_style_incorrect.ics'), 'rb').read() # As defined in namedprops.h DISPID_ALL_DAY_EVENT = 0x8215 EXPECTED_MESSAGES = 1 START_DATE_PT_SYSTIME = 132587712000000000 END_DATE_PT_SYSTIME = 132588576000000000 ALL_DAY_NAMEID = MAPINAMEID(PSETID_Appointment, MNID_ID, DISPID_ALL_DAY_EVENT) ALL_DAY_PROP = CHANGE_PROP_TYPE( store.GetIDsFromNames([ALL_DAY_NAMEID], MAPI_CREATE)[0], PT_BOOLEAN ) prop_dic = { PR_START_DATE: [FileTime(START_DATE_PT_SYSTIME)], PR_END_DATE: [FileTime(END_DATE_PT_SYSTIME)], ALL_DAY_PROP: [False] } assert_item_count_from_ical(icaltomapi, ical, EXPECTED_MESSAGES) assert_properties_from_ical(icaltomapi, message, prop_dic) # Reverting back to ical. mapitoical.AddMessage(message, '', 0) method, converted_ical = mapitoical.Finalize(icalmapi.M2IC_NO_VTIMEZONE) split_ical = converted_ical.split(b'\r\n') assert b'PUBLISH' == method assert b'DTSTART:20210226T000000Z' in split_ical assert b'DTEND:20210227T000000Z' in split_ical
def _name_to_proptag(proptag, mapiobj, proptype=None): ptype, namespace, name = _split_proptag(proptag) proptype = proptype or ptype if name.isdigit(): # XXX name = int(name) elif name.startswith('0x'): name = int(name, 16) if namespace in NAMESPACE_GUID: guid = NAMESPACE_GUID[namespace] elif namespace in STR_GUID: guid = STR_GUID[namespace] if proptype is None: return None, proptype, namespace, name nameid = MAPINAMEID(guid, MNID_ID if isinstance(name, int) else MNID_STRING, name) lpname = mapiobj.GetIDsFromNames( [nameid], 0 ) # TODO MAPI_CREATE, or too dangerous because of potential db overflow? proptag = CHANGE_PROP_TYPE(lpname[0], proptype) return proptag, proptype, namespace, name
def __init__(self, parent_mapiobj, mapiobj): self._parent_mapiobj = parent_mapiobj self.proptag = mapiobj.ulPropTag if PROP_TYPE( mapiobj.ulPropTag ) == PT_ERROR and mapiobj.Value == MAPI_E_NOT_ENOUGH_MEMORY: if PROP_ID( self.proptag) == PROP_ID(PR_BODY_W): # avoid slow guessing self.proptag = PR_BODY_W mapiobj = SPropDelayedValue(parent_mapiobj, self.proptag) elif PROP_ID(self.proptag) in (PROP_ID(PR_RTF_COMPRESSED), PROP_ID(PR_HTML)): self.proptag = CHANGE_PROP_TYPE(self.proptag, PT_BINARY) mapiobj = SPropDelayedValue(parent_mapiobj, self.proptag) else: # XXX possible to use above trick to infer all proptags? for proptype in (PT_BINARY, PT_UNICODE): # XXX slow, incomplete? proptag = (mapiobj.ulPropTag & 0xffff0000) | proptype try: HrGetOneProp( parent_mapiobj, proptag ) # XXX: Unicode issue?? calls GetProps([proptag], 0) self.proptag = proptag # XXX isn't it strange we can get here except MAPIErrorNotEnoughMemory: mapiobj = SPropDelayedValue(parent_mapiobj, proptag) self.proptag = proptag break except MAPIErrorNotFound: pass self.mapiobj = mapiobj self._value = None self.__lpname = None
def enabled(self): """Auto-processing is enabled.""" prop = CHANGE_PROP_TYPE(self._ids[0], PT_BOOLEAN) try: return HrGetOneProp(self._fb, prop).Value except MAPIErrorNotFound: return True
def archive_folder(self): """ Archive :class:`Folder` """ ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) # XXX merge namedprops stuff PROP_STORE_ENTRYIDS = CHANGE_PROP_TYPE(ids[0], PT_MV_BINARY) PROP_ITEM_ENTRYIDS = CHANGE_PROP_TYPE(ids[1], PT_MV_BINARY) try: # support for multiple archives was a mistake, and is not and _should not_ be used. so we just pick nr 0. arch_storeid = HrGetOneProp(self.mapiobj, PROP_STORE_ENTRYIDS).Value[0] arch_folderid = HrGetOneProp(self.mapiobj, PROP_ITEM_ENTRYIDS).Value[0] except MAPIErrorNotFound: return archive_store = self.server._store2(arch_storeid) return _store.Store(mapiobj=archive_store, server=self.server).folder(entryid=_benc(arch_folderid))
def __init__(self, parent_mapiobj, mapiobj): self._parent_mapiobj = parent_mapiobj #: MAPI proptag, for example 0x37001f for PR_SUBJECT_W self.proptag = mapiobj.ulPropTag if (PROP_TYPE(mapiobj.ulPropTag) == PT_ERROR and \ mapiobj.Value == MAPI_E_NOT_ENOUGH_MEMORY): # avoid slow guessing if PROP_ID(self.proptag) == PROP_ID(PR_BODY_W): self.proptag = PR_BODY_W mapiobj = SPropDelayedValue(parent_mapiobj, self.proptag) elif PROP_ID(self.proptag) in \ (PROP_ID(PR_RTF_COMPRESSED), PROP_ID(PR_HTML)): self.proptag = CHANGE_PROP_TYPE(self.proptag, PT_BINARY) mapiobj = SPropDelayedValue(parent_mapiobj, self.proptag) else: # TODO possible to use above trick to infer all proptags? # TODO slow, incomplete? for proptype in (PT_BINARY, PT_UNICODE): proptag = (mapiobj.ulPropTag & 0xffff0000) | proptype try: # TODO: Unicode issue?? calls GetProps([proptag], 0) HrGetOneProp(parent_mapiobj, proptag) # TODO how did we end up here, why is this possible self.proptag = proptag except MAPIErrorNotEnoughMemory: mapiobj = SPropDelayedValue(parent_mapiobj, proptag) self.proptag = proptag break except MAPIErrorNotFound: pass self.mapiobj = mapiobj self._value = None self.__lpname = None
def primary_item(self): ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, MAPI_CREATE) # XXX merge namedprops stuff PROP_REF_ITEM_ENTRYID = CHANGE_PROP_TYPE(ids[4], PT_BINARY) entryid = HrGetOneProp(self.mapiobj, PROP_REF_ITEM_ENTRYID).Value try: return self.folder.primary_store.item(entryid=_benc(entryid)) except NotFoundError: pass
def categories(self): """Categories.""" proptag = self.mapiobj.GetIDsFromNames([NAMED_PROP_CATEGORY], MAPI_CREATE)[0] proptag = CHANGE_PROP_TYPE(proptag, PT_MV_UNICODE) try: value = self.prop(proptag).value except NotFoundError: value = [] return PersistentList(self.mapiobj, proptag, value)
def primary_store(self): ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) # XXX merge namedprops stuff PROP_REF_STORE_ENTRYID = CHANGE_PROP_TYPE(ids[3], PT_BINARY) try: entryid = HrGetOneProp(self.mapiobj, PROP_REF_STORE_ENTRYID).Value except MAPIErrorNotFound: return return _store.Store(entryid=_benc(entryid), server=self.server)
def restore(self): ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) # XXX cache folder.GetIDs..? PROP_STUBBED = CHANGE_PROP_TYPE(ids[2], PT_BOOLEAN) PROP_REF_STORE_ENTRYID = CHANGE_PROP_TYPE(ids[3], PT_BINARY) PROP_REF_ITEM_ENTRYID = CHANGE_PROP_TYPE(ids[4], PT_BINARY) PROP_REF_PREV_ENTRYID = CHANGE_PROP_TYPE(ids[5], PT_BINARY) PROP_FLAGS = CHANGE_PROP_TYPE(ids[6], PT_LONG) # get/create primary item primary_item = self.primary_item if not primary_item: mapiobj = self.folder.primary_folder.mapiobj.CreateMessage(None, 0) new = True else: mapiobj = primary_item.mapiobj new = False if not new and not primary_item.stubbed: return # cleanup primary item mapiobj.DeleteProps([PROP_STUBBED, PR_ICON_INDEX]) at = Table(self.server, mapiobj.GetAttachmentTable(0), columns=[PR_ATTACH_NUM]) for row in at.rows(): mapiobj.DeleteAttach(row[0].value, 0, None, 0) # copy contents into it exclude_props = [ PROP_REF_STORE_ENTRYID, PROP_REF_ITEM_ENTRYID, PROP_REF_PREV_ENTRYID, PROP_FLAGS ] self.mapiobj.CopyTo(None, exclude_props, 0, None, IID_IMessage, mapiobj, 0) mapiobj.SaveChanges(KEEP_OPEN_READWRITE) # update backref if new: entryid = HrGetOneProp(mapiobj, PR_ENTRYID).Value self.mapiobj.SetProps([SPropValue(PROP_REF_ITEM_ENTRYID, entryid)]) self.mapiobj.SaveChanges(KEEP_OPEN_READWRITE)
def stubbed(self): """Item is stubbed by archiver.""" # TODO caching folder.GetIDs..? ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, MAPI_CREATE) PROP_STUBBED = CHANGE_PROP_TYPE(ids[2], PT_BOOLEAN) try: # False means destubbed return HrGetOneProp(self.mapiobj, PROP_STUBBED).Value except MAPIErrorNotFound: return False
def stubbed(self): """ Is item stubbed by archiver? """ ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) # XXX cache folder.GetIDs..? PROP_STUBBED = CHANGE_PROP_TYPE(ids[2], PT_BOOLEAN) try: return HrGetOneProp(self.mapiobj, PROP_STUBBED).Value # False means destubbed except MAPIErrorNotFound: return False
def primary_store(self): """Primary :class:`store <Store>` (for archive folders).""" # TODO merge namedprops stuff ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, MAPI_CREATE) PROP_REF_STORE_ENTRYID = CHANGE_PROP_TYPE(ids[3], PT_BINARY) try: entryid = HrGetOneProp(self.mapiobj, PROP_REF_STORE_ENTRYID).Value except MAPIErrorNotFound: return return _store.Store(entryid=_benc(entryid), server=self.server)
def primary_folder(self): ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) # XXX merge namedprops stuff PROP_REF_ITEM_ENTRYID = CHANGE_PROP_TYPE(ids[4], PT_BINARY) entryid = HrGetOneProp(self.mapiobj, PROP_REF_ITEM_ENTRYID).Value if self.primary_store: try: return self.primary_store.folder(entryid=_hex(entryid)) except NotFoundError: pass
def archive_store(self): """Archive :class:`Store`.""" ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, 0) # XXX merge namedprops stuff PROP_STORE_ENTRYIDS = CHANGE_PROP_TYPE(ids[0], PT_MV_BINARY) try: # support for multiple archives was a mistake, and is not and _should not_ be used. so we just pick nr 0. entryid = HrGetOneProp(self.mapiobj, PROP_STORE_ENTRYIDS).Value[0] except MAPIErrorNotFound: return return Store(entryid=_benc(entryid), server=self.server) # XXX server?
def primary_folder(self): """Primary :class:`folder <Folder>` (for archive folders).""" # TODO merge namedprops stuff ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, MAPI_CREATE) PROP_REF_ITEM_ENTRYID = CHANGE_PROP_TYPE(ids[4], PT_BINARY) try: entryid = HrGetOneProp(self.mapiobj, PROP_REF_ITEM_ENTRYID).Value except MAPIErrorNotFound: return if self.primary_store: try: return self.primary_store.folder(entryid=_benc(entryid)) except NotFoundError: pass
def archive_folder(self): """Archive :class:`Folder` (in case multiple stores are archived to a single archive store).""" # TODO merge namedprops stuff ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_ARCHIVER, MAPI_CREATE) PROP_ITEM_ENTRYIDS = CHANGE_PROP_TYPE(ids[1], PT_MV_BINARY) try: # support for multiple archives was a mistake, and is not and # _should not_ be used. so we just pick nr 0. arch_folderid = HrGetOneProp(self.mapiobj, PROP_ITEM_ENTRYIDS).Value[0] except MAPIErrorNotFound: return return self.archive_store.folder(entryid=_benc(arch_folderid))
def test_microsoft_style_not_all_day_if_not_midnight_incorrect(store, icaltomapi, message): ical = open(os.path.join(dir_path, 'ics/allday_ms_style_incorrect.ics'), 'rb').read() # As defined in namedprops.h DISPID_ALL_DAY_EVENT = 0x8215 ALL_DAY_NAMEID = MAPINAMEID(PSETID_Appointment, MNID_ID, DISPID_ALL_DAY_EVENT) ALL_DAY_PROP = CHANGE_PROP_TYPE( store.GetIDsFromNames([ALL_DAY_NAMEID], MAPI_CREATE)[0], PT_BOOLEAN ) EXPECTED_MESSAGES = 1 IS_ALL_DAY = [False] assert_item_count_from_ical(icaltomapi, ical, EXPECTED_MESSAGES) assert_property_value_from_ical(icaltomapi, message, ALL_DAY_PROP, IS_ALL_DAY)
def licenseinfo(self): ids = self.mapiobj.GetIDsFromNames(NAMED_PROPS_KC, MAPI_CREATE) proptype = CHANGE_PROP_TYPE(ids[1], PT_TSTRING) prop = HrGetOneProp(self.mapiobj, proptype) return json.loads(prop.Value)
def restore(self, data_path): """ restore data from backup """ self.restored_sourcekeys = set() self.options.sourcekeys = [sk.upper() for sk in self.options.sourcekeys] # determine store to restore to self.log.info('starting restore of %s', data_path) username = os.path.split(data_path)[1] try: if self.options.users: store = self._store(self.options.users[0]) elif self.options.stores: store = self.server.store(self.options.stores[0]) else: store = self._store(username) except kopano.NotFoundError: store = None if not store: fatal('unable to open store (username: %s)' % username) user = store.user # determine stored and specified folders path_folder = folder_struct(data_path, self.options) paths = self.options.folders or sorted(path_folder.keys()) if self.options.recursive: paths = [path2 for path2 in path_folder for path in paths if (path2+'//').startswith(path+'/')] for path in paths: if path not in path_folder: fatal('no such folder: %s' % path) # start restore self.log.info('restoring to store %s', store.entryid) t0 = time.time() stats = {'changes': 0, 'errors': 0} # determine restore root if self.options.restore_root: restore_root = store.folder(self.options.restore_root, create=True) else: restore_root = store.subtree # check existing folders sk_folder = {} for folder in restore_root.folders(): orig_sk = folder.get(PR_EC_BACKUP_SOURCE_KEY) if orig_sk: sk_folder[orig_sk] = folder # restore specified (parts of) folders meta_folders = [] sks = set() for path in paths: fpath = path_folder[path] folderprops = pickle_loads(open('%s/folder' % fpath, 'rb').read()) folder_sk = folderprops[PR_SOURCE_KEY] # determine folder to restore if self.options.sourcekeys: with closing(dbopen(fpath+'/items')) as db: if not [sk for sk in self.options.sourcekeys if sk.encode('ascii') in db]: continue else: if self.options.deletes in (None, 'no') and folder_deleted(fpath): continue sks.add(folder_sk) folder = restore_root.get_folder(path) if (folder and not store.public and \ ((self.options.skip_junk and folder == store.junk) or \ (self.options.skip_deleted and folder == store.wastebasket))): continue # restore folder if self.options.only_meta: # TODO create empty folders with acls/rules, or skip non-existing folders? folder = restore_root.get_folder(path) else: # differential folder move folder = sk_folder.get(folder_sk) if folder and self.options.differential: restore_path = self.options.restore_root+'/'+path if self.options.restore_root else path restore_parent_path = '/'.join(UNESCAPED_SLASH_RE.split(restore_path)[:-1]) if folder.parent.path != restore_parent_path: newparent = store.get_folder(restore_parent_path) if newparent: self.log.info('moving folder %s to %s', folder.path, restore_path) folder.parent.move(folder, newparent) else: folder = restore_root.folder(path, create=True) if self.options.clean_folders: self.log.info('emptying folder %s', folder.path) folder.empty() self.restore_folder(folder, path, fpath, store, store.subtree, stats, user, self.server) if folder: meta_folders.append((folder, fpath)) # differential folder deletes if self.options.differential: for sk in set(sk_folder)-sks: path = sk_folder[sk].path parent = store.get_folder('/'.join(UNESCAPED_SLASH_RE.split(path)[:-1])) if parent: self.log.info('deleting folder %s', path) parent.delete(sk_folder[sk]) # restore folder-level metadata if not (self.options.sourcekeys or self.options.skip_meta): self.log.info('restoring metadata') for (folder, fpath) in meta_folders: folder.permissions_loads(open(fpath+'/acl', 'rb').read(), stats=stats) folder.rules_loads(open(fpath+'/rules', 'rb').read(), stats=stats) # restore store-level metadata (webapp/mapi settings) if user and not (self.options.folders or self.options.restore_root or self.options.skip_meta or self.options.sourcekeys): if os.path.exists('%s/store' % data_path): storeprops = pickle_loads(open('%s/store' % data_path, 'rb').read()) for proptag in SETTINGS_PROPTAGS: if PROP_TYPE(proptag) == PT_TSTRING: proptag = CHANGE_PROP_TYPE(proptag, PT_UNICODE) value = storeprops.get(proptag) if not value: continue store.mapiobj.SetProps([SPropValue(proptag, value)]) store.mapiobj.SaveChanges(KEEP_OPEN_READWRITE) if os.path.exists('%s/delegates' % data_path): store.delegations_loads(open('%s/delegates' % data_path, 'rb').read(), stats=stats) if os.path.exists('%s/acl' % data_path): store.permissions_loads(open('%s/acl' % data_path, 'rb').read(), stats=stats) for sourcekey in self.options.sourcekeys: if sourcekey not in self.restored_sourcekeys: self.log.error('could not restore sourcekey: %s', sourcekey) self.log.info('restore completed in %.2f seconds (%d changes, ~%.2f/sec, %d errors)', time.time()-t0, stats['changes'], stats['changes']/(time.time()-t0), stats['errors'])
def enabled(self, b): prop = CHANGE_PROP_TYPE(self._ids[0], PT_BOOLEAN) self._fb.SetProps([SPropValue(prop, b)]) _utils._save(self._fb)
def categories(self, value): proptag = self.mapiobj.GetIDsFromNames([NAMED_PROP_CATEGORY], MAPI_CREATE)[0] proptag = CHANGE_PROP_TYPE(proptag, PT_MV_UNICODE) data = [_unicode(x) for x in value] self.mapiobj.SetProps([SPropValue(proptag, data)]) _utils._save(self.mapiobj)
def categories(self, value): proptag = self.mapiobj.GetIDsFromNames([NAMED_PROP_CATEGORY], MAPI_CREATE)[0] proptag = CHANGE_PROP_TYPE(proptag, PT_MV_STRING8) self.mapiobj.SetProps([SPropValue(proptag, list(value))]) self.mapiobj.SaveChanges(KEEP_OPEN_READWRITE)
def restore(self, data_path): """ restore data from backup """ # determine store to restore to self.log.info('starting restore of %s', data_path) username = os.path.split(data_path)[1] try: if self.options.users: store = self._store(self.options.users[0]) elif self.options.stores: store = self.server.store(self.options.stores[0]) else: store = self._store(username) except kopano.NotFoundError: store = None if not store: fatal('unable to open store (username: %s)' % username) user = store.user # determine stored and specified folders path_folder = folder_struct(data_path, self.options) paths = self.options.folders or sorted(path_folder.keys()) if self.options.recursive: paths = [path2 for path2 in path_folder for path in paths if (path2+'//').startswith(path+'/')] for path in paths: if path not in path_folder: fatal('no such folder: %s' % path) # start restore self.log.info('restoring to store %s', store.entryid) t0 = time.time() stats = {'changes': 0, 'errors': 0} # restore specified (parts of) folders restored = [] for path in paths: fpath = path_folder[path] restore_path = _decode(self.options.restore_root)+'/'+path if self.options.restore_root else path if self.options.sourcekeys: with closing(dbopen(fpath+'/items')) as db: if not [sk for sk in self.options.sourcekeys if sk.encode('ascii') in db]: continue else: if self.options.deletes in (None, 'no') and folder_deleted(fpath): continue folder = store.subtree.get_folder(restore_path) if (folder and not store.public and \ ((self.options.skip_junk and folder == store.junk) or \ (self.options.skip_deleted and folder == store.wastebasket))): continue if not self.options.only_meta: folder = store.subtree.folder(restore_path, create=True) self.restore_folder(folder, path, fpath, store, store.subtree, stats, user, self.server) restored.append((folder, fpath)) # restore folder-level metadata if not (self.options.sourcekeys or self.options.skip_meta): self.log.info('restoring metadata') for (folder, fpath) in restored: load_acl(folder, user, self.server, open(fpath+'/acl', 'rb').read(), stats, self.log) load_rules(folder, user, self.server, open(fpath+'/rules', 'rb').read(), stats, self.log) # restore store-level metadata (webapp/mapi settings) if user and not self.options.folders and not self.options.restore_root and not self.options.skip_meta: if os.path.exists('%s/store' % data_path): storeprops = pickle_loads(open('%s/store' % data_path, 'rb').read()) for proptag in WEBAPP_SETTINGS + (PR_EC_OUTOFOFFICE_SUBJECT, PR_EC_OUTOFOFFICE_MSG, PR_EC_OUTOFOFFICE, PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL): if PROP_TYPE(proptag) == PT_TSTRING: proptag = CHANGE_PROP_TYPE(proptag, PT_UNICODE) value = storeprops.get(proptag) if not value: continue store.mapiobj.SetProps([SPropValue(proptag, value)]) store.mapiobj.SaveChanges(KEEP_OPEN_READWRITE) if os.path.exists('%s/delegates' % data_path): load_delegates(user, self.server, open('%s/delegates' % data_path, 'rb').read(), stats, self.log) if os.path.exists('%s/acl' % data_path): load_acl(store, user, self.server, open('%s/acl' % data_path, 'rb').read(), stats, self.log) self.log.info('restore completed in %.2f seconds (%d changes, ~%.2f/sec, %d errors)', time.time()-t0, stats['changes'], stats['changes']/(time.time()-t0), stats['errors'])