# Filter, dump messages to and from Outlook Mail folders
# Author: Sean D. True, WebReply.Com
# October, 2002
# Copyright PSF, license under the PSF license

try:
    True, False
except NameError:
    # Maintain compatibility with Python 2.2
    True, False = 1, 0

def filter_message(msg, mgr, all_actions=True):
    config = mgr.config.filter
    prob = mgr.score(msg)
    prob_perc = prob * 100
    if prob_perc >= config.spam_threshold:
        disposition = "Yes"
        attr_prefix = "spam"
    elif prob_perc >= config.unsure_threshold:
        disposition = "Unsure"
        attr_prefix = "unsure"
    else:
        disposition = "No"
        attr_prefix = None

    ms = mgr.message_store
    try:
        try:
            # Save the score
            # Catch msgstore exceptions, as failing to save the score need
            # not be fatal - it may still be possible to perform the move.
            if config.save_spam_info:
                # The object can sometimes change underneath us (most
                # noticably Hotmail, but reported in other cases too).
                # Retry 3 times handling ObjectChanged exception.
                # Why 3?  Why not!
                for i in range(3):
                    try:
                        msg.SetField(mgr.config.general.field_score_name, prob)
                        # and the ID of the folder we were in when scored.
                        # (but only if we want to perform all actions)
                        # Note we must do this, and the Save, before the
                        # filter, else the save will fail.
                        if all_actions:
                            msg.RememberMessageCurrentFolder()
                        msg.Save()
                        break
                    except ms.ObjectChangedException:
                        # Someone has changed the message underneath us.
                        # The general solution is to re-open the message, and
                        # try again.  We reach into our knowledge of the
                        # message to force this.
                        mgr.LogDebug(1, "Got ObjectChanged changed - " \
                                        "trying again...")
                        msg.dirty = False
                        msg.mapi_object = None # cause it to be re-fetched.
                else:
                    # Give up trying to save the score.
                    mgr.LogDebug(0, "Got ObjectChanged 3 times in a row - " \
                                    "giving up!")
                    msg.dirty = False
        except ms.ReadOnlyException:
            # read-only message - not much we can do!
            # Clear dirty flag anyway
            mgr.LogDebug(1, "Message is read-only - could not save Spam score")
            msg.dirty = False
        except ms.MsgStoreException, details:
            # Some other error saving - this is nasty.
            print "Unexpected MAPI error saving the spam score for", msg
            print details
            # Clear dirty flag anyway
            msg.dirty = False

        if all_actions and attr_prefix is not None:
            folder_id = getattr(config, attr_prefix + "_folder_id")
            action = getattr(config, attr_prefix + "_action").lower()
            mark_as_read = getattr(config, attr_prefix + "_mark_as_read")
            if mark_as_read:
                msg.SetReadState(True)
            if action.startswith("un"): # untouched
                mgr.LogDebug(1, "Not touching message '%s'" % msg.subject)
            elif action.startswith("co"): # copied
                try:
                    dest_folder = ms.GetFolder(folder_id)
                except ms.MsgStoreException:
                    print "ERROR: Unable to open the folder to Copy the " \
                          "message - this message was not copied"
                else:
                    msg.CopyToReportingError(mgr, dest_folder)
                    mgr.LogDebug(1, "Copied message '%s' to folder '%s'" \
                                 % (msg.subject, dest_folder.GetFQName()))
            elif action.startswith("mo"): # Moved
                try:
                    dest_folder = ms.GetFolder(folder_id)
                except ms.MsgStoreException:
                    print "ERROR: Unable to open the folder to Move the " \
                          "message - this message was not moved"
                else:
                    msg.MoveToReportingError(mgr, dest_folder)
                    mgr.LogDebug(1, "Moved message '%s' to folder '%s'" \
                                 % (msg.subject, dest_folder.GetFQName()))
            else:
                raise RuntimeError, "Eeek - bad action '%r'" % (action,)

        if all_actions:
            mgr.stats.RecordClassification(prob)
        return disposition
    except:
        print "Failed filtering message!", msg
        import traceback
        traceback.print_exc()
        return "Failed"

def filter_folder(f, mgr, config, progress):
    only_unread = config.only_unread
    only_unseen = config.only_unseen
    all_actions = config.action_all
    dispositions = {}
    field_name = mgr.config.general.field_score_name
    for message in f.GetMessageGenerator():
        if progress.stop_requested():
            break
        progress.tick()
        if only_unread and message.GetReadState() or \
           only_unseen and message.GetField(field_name) is not None:
            continue
        try:
            disposition = filter_message(message, mgr, all_actions)
        except:
            import traceback
            print "Error filtering message '%s'" % (message,)
            traceback.print_exc()
            disposition = "Error"

        dispositions[disposition] = dispositions.get(disposition, 0) + 1

    return dispositions

# Called for "filter now"
def filterer(mgr, config, progress):
    config = config.filter_now
    if not config.folder_ids:
        progress.error("You must specify at least one folder")
        return

    progress.set_status("Counting messages")
    num_msgs = 0
    for f in mgr.message_store.GetFolderGenerator(config.folder_ids, config.include_sub):
        num_msgs += f.count
    progress.set_max_ticks(num_msgs+3)
    dispositions = {}
    for f in mgr.message_store.GetFolderGenerator(config.folder_ids, config.include_sub):
        progress.set_status("Filtering folder '%s'" % (f.name))
        this_dispositions = filter_folder(f, mgr, config, progress)
        for key, val in this_dispositions.items():
            dispositions[key] = dispositions.get(key, 0) + val
        if progress.stop_requested():
            return
    # All done - report what we did.
    err_text = ""
    if dispositions.has_key("Error"):
        err_text = " (%d errors)" % dispositions["Error"]
    dget = dispositions.get
    text = "Found %d spam, %d unsure and %d good messages%s" % \
                (dget("Yes",0), dget("Unsure",0), dget("No",0), err_text)
    progress.set_status(text)

def main():
    print "Sorry - we don't do anything here any more"

if __name__ == "__main__":
    main()


syntax highlighted by Code2HTML, v. 0.9.1