-1

I am trying to delete messages in an Exchange folder until the folder size reaches a specified value in bytes.

Here is my example code:

def get_foldersize(folder):
    if folder.__class__ != folders.Messages:
        return -1
    return sum(folder.all().values_list('size', flat=True))


def wipe_by_size(folder=account.inbox, size='4.8gb', result_as_str=True):
    """
    Delete all old messages in specified exchange folder to specified size
    size may setted like str ("4,9gb", "500.5 Mb", "1024kB") or like int (means size setted in bytes)
    if result_as_str == True, func returns a stats delete msg, 
    otherwise tuple (deleted_files_count, deleted_bytes, before_files_count, before_bytes, after_files_count, after_bytes)
    """
    if folder.__class__ != folders.Messages:
        raise ValueError("Param 'folder' not a exchange-server folder object!")

    m = {
        'gb': 1024 ** 3,
        'mb': 1024 ** 2,
        'kb': 1024,
    }

    size_bytes = None

    if isinstance(size, str):
        if ',' in size:
            size = size.replace(',', '.')

        for k, v in m.items():
            if k in size.lower():
                size = size.split(k)[0].strip()
                try:
                    size_float = float(size)
                    size_bytes = round(size_float * v)
                except ValueError:
                    raise ValueError(f"Incorrect format of param 'size', recieve '{size_str}'")
                break
        else:
            raise ValueError(
                f"Incorrect format of 'size' param, '{size_str}' must contains int or float number and something from '{list(m.keys())}'")
    elif isinstance(size, (int, float,)):
        size_bytes = int(size)
    else:
        raise ValueError(
            'Parameter "size" can be a str (example:"4,9gb", "500.5 Mb", "1024kB") or int folder size in bytes')

    if get_foldersize(folder) < size_bytes:
        return

    before = folder.total_count
    before_bytes = get_foldersize(folder)

    while get_foldersize(folder) > size_bytes:
        msg = folder.all().order_by('datetime_received')[0]
        try:
            # print(msg.subject, msg.sender)
            msg.delete()
        except Exception as e:
            if msg is not None:
                print(f"wipe_by_size: Can't delete {msg.sender}({msg.subject})")
            else:
                print("wipe_by_size: msg = None")

    after = folder.total_count
    deleted = before - after

    after_bytes = get_foldersize(folder)
    deleted_bytes = before_bytes - after_bytes

    if result_as_str:
        return f"""Wiped {deleted} file(s), freed {'{0:12,d}'.format(
            deleted_bytes)} bytes. Before: {before} files, {'{0:12,d}'.format(
            before_bytes)} bytes. After: {after} files, {'{0:12,d}'.format(after_bytes)} bytes"""

    return deleted, deleted_bytes, before, before_bytes, after, after_bytes


delete_msg = wipe_by_size(folder=account.sent, size='4,8gb')
deleted, deleted_bytes, before, before_bytes, after, after_bytes = wipe_by_size(folder=account.sent, size='4,8gb')

The function works but very slowly - slowly because get_foldersize() is called for each iteration of the while-loop.

I suggest I need to get all messages in the folder in reverse order(like now), store the size of each message, then calculate how many and which messages I need to wipe and delete them all in one go. I think that will more faster. But I don't know how (and if it's possible?) to get the message size.

Erik Cederstrand
  • 9,643
  • 8
  • 39
  • 63
  • In the future, try to limit your code to the smallest possible example that still shows the error. There are some tips at https://stackoverflow.com/help/mcve – Erik Cederstrand Mar 12 '19 at 09:46

1 Answers1

0

The message size is contained in the size field, as you found out. So you could calculate the folder size once, and then read msg.size and decrement the total folder size in a loop. Finally, reduce network traffic by only getting the size field of each message in the folder:

class FolderSize(ExtendedProperty):
    property_tag = 0x0e08
    property_type = 'Integer'

Folder.register('size', FolderSize)

folder = account.inbox
folder_size = folder.size
max_size = 123456789  # Bytes

for msg in folder.all().only('size'):
    if folder_size <= max_size:
        break
    msg.delete()
    folder_size -= msg.size
Erik Cederstrand
  • 9,643
  • 8
  • 39
  • 63
  • thank you very much! ) can you help with check of folders instance in get_foldersize()? because if folder.__class__ != folders.Messages: didn't pass an acount.sent folder :/ – groshevpavel Mar 15 '19 at 10:56
  • In that case, I would suggest fetching the size of the folder directly, using extended properties. I have updated the example above (the `FolderSize` part comes from the exchangelib README). – Erik Cederstrand Mar 15 '19 at 15:54
  • sorry, Erik, but i'am afraid folder.size returns size of all letters but without attachments of each letter :/ because i get a much more different values from folder.size and in Outlook interface at folder properties, folder size window.. Outlook reports 4945451 KB but folder.size is about 70Mb.. i have folder with many letters each one has at least one attachment of 2-20Mb.. can you help?.. i try to register PidTagAttachSize Canonical Property at Message but unsuccessfull :/ thank you! – groshevpavel Mar 25 '19 at 07:41
  • In that case, I would just sum the individual message sizes to get the true folder size: `sum(folder.all().values_list('size', flat=True))`. – Erik Cederstrand Mar 26 '19 at 06:38