async def get(self, uid: int, cached_msg: CachedMessage = None, requirement: FetchRequirement = FetchRequirement.METADATA) \ -> Optional[Message]: redis = self._redis keys = self._keys ns_keys = self._ns_keys msg_keys = MessageKeys(keys, uid) await redis.unwatch() multi = redis.multi_exec() multi.sismember(keys.uids, uid) multi.smembers(msg_keys.flags) multi.hmget(msg_keys.immutable, b'time', b'emailid', b'threadid') multi.get(keys.abort) exists, flags, (time, email_id, thread_id), abort = \ await multi.execute() MailboxAbort.assertFalse(abort) if not exists: if cached_msg is not None: if not isinstance(cached_msg, Message): raise TypeError(cached_msg) return Message.copy_expunged(cached_msg) else: return None msg_flags = {Flag(flag) for flag in flags} msg_email_id = ObjectId.maybe(email_id) msg_thread_id = ObjectId.maybe(thread_id) msg_time = datetime.fromisoformat(time.decode('ascii')) return Message(uid, msg_time, msg_flags, email_id=msg_email_id, thread_id=msg_thread_id, redis=redis, ns_keys=ns_keys)
async def _load_demo_mailbox(cls, resource: str, name: str, mbx: MailboxData) -> None: path = os.path.join('demo', name) msg_names = sorted(resource_listdir(resource, path)) for msg_name in msg_names: if msg_name == '.readonly': mbx._readonly = True continue elif msg_name.startswith('.'): continue msg_path = os.path.join(path, msg_name) with closing(resource_stream(resource, msg_path)) as msg_stream: flags_line = msg_stream.readline() msg_timestamp = float(msg_stream.readline()) msg_data = msg_stream.read() msg_dt = datetime.fromtimestamp(msg_timestamp, timezone.utc) msg_flags = {Flag(flag) for flag in flags_line.split()} if Recent in msg_flags: msg_flags.remove(Recent) msg_recent = True else: msg_recent = False msg = AppendMessage(msg_data, msg_dt, frozenset(msg_flags), ExtensionOptions.empty()) email_id, thread_id, ref = await mbx.save(msg_data) prepared = PreparedMessage(msg_dt, msg.flag_set, email_id, thread_id, msg.options, ref) await mbx.add(prepared, recent=msg_recent)
def open(cls: Type[_MFT], base_dir: str, fp: IO[str]) -> _MFT: ret = [] for line in fp: i, kwd = line.split() if kwd.startswith('\\'): raise ValueError(kwd) ret.append((i, kwd)) return cls([Flag(kwd) for _, kwd in sorted(ret)])
async def _get_updated(self, last_mod_seq: int) \ -> Tuple[int, Sequence[Message], Sequence[int]]: redis = self._redis keys = self._keys ns_keys = self._ns_keys while True: pipe = watch_pipe(redis, keys.max_mod, keys.abort) pipe.zrangebyscore(keys.mod_seq, last_mod_seq) pipe.get(keys.abort) _, _, uids, abort = await pipe.execute() MailboxAbort.assertFalse(abort) multi = redis.multi_exec() multi.get(keys.max_mod) multi.zrangebyscore(keys.expunged, last_mod_seq) for uid in uids: msg_keys = MessageKeys(keys, uid) multi.echo(uid) multi.smembers(msg_keys.flags) multi.hmget(msg_keys.immutable, b'time', b'emailid', b'threadid') try: results = await multi.execute() except MultiExecError: if await check_errors(multi): raise else: break mod_seq = int(results[0] or 0) expunged = [int(uid) for uid in results[1]] updated: List[Message] = [] for i in range(2, len(results), 3): msg_uid = int(results[i]) msg_flags = {Flag(flag) for flag in results[i + 1]} time_b, email_id, thread_id = results[i + 2] msg_time = datetime.fromisoformat(time_b.decode('ascii')) msg = Message(msg_uid, msg_time, msg_flags, email_id=ObjectId(email_id), thread_id=ObjectId(thread_id), redis=redis, ns_keys=ns_keys) updated.append(msg) return mod_seq, updated, expunged
def to_maildir(self, flags: Iterable[Union[bytes, Flag]]) -> str: """Return the string of letter codes that are used to map to defined IMAP flags and keywords. Args: flags: The flags and keywords to map. """ codes = [] for flag in flags: if isinstance(flag, bytes): flag = Flag(flag) from_sys = self._from_sys.get(flag) if from_sys is not None: codes.append(from_sys) else: from_kwd = self._from_kwd.get(flag) if from_kwd is not None: codes.append(from_kwd) return ''.join(codes)
async def update_flags(self, messages: Sequence[Message], flag_set: FrozenSet[Flag], mode: FlagOp) -> None: redis = self._redis keys = self._keys messages = list(messages) if not messages: return uids = {msg.uid: msg for msg in messages} while True: pipe = watch_pipe(redis, keys.max_mod, keys.abort) pipe.smembers(keys.uids) pipe.mget(keys.max_mod, keys.abort) _, _, existing_uids, (max_mod, abort) = await pipe.execute() MailboxAbort.assertFalse(abort) update_uids = uids.keys() & {int(uid) for uid in existing_uids} if not update_uids: break new_mod = int(max_mod or 0) + 1 new_flags: Dict[int, Awaitable[Sequence[bytes]]] = {} multi = redis.multi_exec() multi.set(keys.max_mod, new_mod) for msg in messages: msg_uid = msg.uid if msg_uid not in update_uids: continue msg_keys = MessageKeys(keys, msg_uid) flag_vals = (flag.value for flag in flag_set) multi.zadd(keys.mod_seq, new_mod, msg_uid) if mode == FlagOp.REPLACE: multi.delete(msg_keys.flags) if flag_set: multi.sadd(msg_keys.flags, *flag_vals) if Deleted in flag_set: multi.sadd(keys.deleted, msg_uid) else: multi.srem(keys.deleted, msg_uid) if Seen not in flag_set: multi.zadd(keys.unseen, msg_uid, msg_uid) else: multi.zrem(keys.unseen, msg_uid) elif mode == FlagOp.ADD and flag_set: multi.sadd(msg_keys.flags, *flag_vals) if Deleted in flag_set: multi.sadd(keys.deleted, msg_uid) if Seen in flag_set: multi.zrem(keys.unseen, msg_uid) elif mode == FlagOp.DELETE and flag_set: multi.srem(msg_keys.flags, *flag_vals) if Deleted in flag_set: multi.srem(keys.deleted, msg_uid) if Seen in flag_set: multi.zadd(keys.unseen, msg_uid, msg_uid) new_flags[msg_uid] = multi.smembers(msg_keys.flags) try: await multi.execute() except MultiExecError: if await check_errors(multi): raise else: for msg_uid, msg_flags in new_flags.items(): msg = uids[msg_uid] msg.permanent_flags = frozenset( Flag(flag) for flag in await msg_flags) break
import unittest from datetime import datetime from pymap.flags import FlagOp, PermanentFlags, SessionFlags from pymap.message import BaseMessage from pymap.parsing.command.select import SearchCommand, UidSearchCommand from pymap.parsing.response import ResponseOk from pymap.parsing.specials import SequenceSet, ObjectId from pymap.parsing.specials.flag import Seen, Flagged, Flag from pymap.selected import SelectedMailbox _Keyword = Flag(b'$Keyword') class _Message(BaseMessage): async def load_content(self, requirement): raise RuntimeError() class TestSelectedMailbox(unittest.TestCase): def setUp(self) -> None: self.response = ResponseOk(b'.', b'testing') @classmethod def new_selected(cls, guid: bytes = b'test') -> SelectedMailbox: return SelectedMailbox(ObjectId(guid), False, PermanentFlags([Seen, Flagged]), SessionFlags([_Keyword])) @classmethod def set_messages(cls, selected: SelectedMailbox, expunged,