kisli / vmime

VMime Mail Library
http://www.vmime.org
GNU General Public License v3.0
274 stars 110 forks source link

Expunge on IMAP-Folder #112

Open SoulfreezerXP opened 9 years ago

SoulfreezerXP commented 9 years ago

Hi Vincent,

I have a question to IMAP. I am fetching "unseen" mails from an IMAP-Exchange-Server. In my fetching-loop I am deleting each mail, cause in another run I do not want fetch them again. I wonder, why there is no "expunge" called, when closing the folder. I have added a "true" to the close-parameter. If I explicitly call "expunge()" from the folder-class, it works fine. Is this ok? Should the user call "expunge()" explicit from the folder-class?

Then I have another question. How can I tag the fetched mails as "SEEN" instead of deleting them?

--> Thanx for your super support! <--


Without explicit "EXPUNGE"

... ... imap:2->(Client)a010 STORE 1 +FLAGS (\Deleted) imap:2->* 1 FETCH (FLAGS (\Seen \Deleted)) imap:2->a010 OK STORE completed. imap:2->(Client)a011 CLOSE imap:2->(Client)a012 LOGOUT imap:2->(Client)Disconnecting imap:1->(Client)a007 LOGOUT imap:1->(Client)Disconnecting


With explicit "EXPUNGE"

imap:2->(Client)a010 STORE 1 +FLAGS (\Deleted) imap:2->* 1 FETCH (FLAGS (\Seen \Deleted)) imap:2->a010 OK STORE completed. imap:2->(Client)a011 EXPUNGE imap:2->* 1 EXPUNGE imap:2->* 1 EXISTS imap:2->a011 OK EXPUNGE completed. imap:2->(Client)a012 CLOSE imap:2->(Client)a013 LOGOUT imap:2->(Client)Disconnecting imap:1->(Client)a007 LOGOUT imap:1->(Client)Disconnecting

Minimal example:

#define VMIME_STATIC                                                                                        
#include <vmime/vmime.hpp>                                                                                  
#include <iostream>                                                                                         

struct MyCV : vmime::security::cert::defaultCertificateVerifier                                             
{                                                                                                           
    void verify(__attribute__((unused))vmime::shared_ptr<vmime::security::cert::certificateChain> c,        
                __attribute__((unused))const vmime::string& h) {}                                           
};                                                                                                          

struct MyTH : vmime::net::timeoutHandler                                                                    
{                                                                                                           
    virtual bool isTimeOut() {return (getTime() >= m_last + 20);}                                           
    virtual void resetTimeOut() {m_last = getTime(); }                                                      
    virtual bool handleTimeOut() {std::cout << "TimeOut!" << getTime() << std::endl; return false;}         
    unsigned long getTime() {return vmime::platform::getHandler()->getUnixTime();}                          
    unsigned long m_last;                                                                                   
};                                                                                                          

struct MyTHF : vmime::net::timeoutHandlerFactory                                                            
{                                                                                                           
    vmime::shared_ptr<vmime::net::timeoutHandler> create() {return vmime::make_shared<MyTH>();}             
};                                                                                                          

struct MyT : vmime::net::tracer                                                                             
{                                                                                                           
    MyT(const vmime::string& proto, const int cId) : m_p(proto), m_cId(cId) {}                              
    void traceSend(const vmime::string& l){traceReceive("(Client)" + l);}                                   
    void traceReceive(const vmime::string& l){std::cout << m_p << ":" << m_cId << "->" << l << std::endl;}  
    const vmime::string m_p;                                                                                
    const int m_cId;                                                                                        
};                                                                                                          

class MyTF : public vmime::net::tracerFactory                                                               
{                                                                                                           
    vmime::shared_ptr<vmime::net::tracer> create(vmime::shared_ptr<vmime::net::service> serv, const int cId)
    {                                                                                                       
        return vmime::make_shared<MyT>(serv->getProtocolName(), cId);                                       
    }                                                                                                       
};                                                                                                          

int main()                                                                                                  
{                                                                                                           
    try                                                                                                     
    {                                                                                                       
        vmime::shared_ptr<vmime::net::session> sess = vmime::make_shared<vmime::net::session>();            
        sess->getProperties().setProperty("store.protocol","imap");                                         

        vmime::shared_ptr<vmime::net::store> store = sess->getStore();                                      
        store->setProperty("server.address","xxxx");                                           
        store->setProperty("server.port",143);                                                              
        store->setProperty("connection.tls", true);                                                         
        store->setProperty("connection.tls.required", true);                                                
        store->setProperty("options.need-authentication", true);                                            
        store->setProperty("auth.username", "xxxx");                                            
        store->setProperty("auth.password", "xxxx");                                              
        store->setProperty("options.chunking",false);                                                       
        store->setCertificateVerifier(vmime::make_shared<MyCV>());                                          
        store->setTimeoutHandlerFactory(vmime::make_shared<MyTHF>());                                       
        store->setTracerFactory(vmime::make_shared<MyTF>());                                                
        store->connect();                                                                                   

        vmime::shared_ptr<vmime::net::folder> folder;                                                       
        folder = store->getDefaultFolder();                                                                 
        folder->open(vmime::net::folder::MODE_READ_WRITE);                                                  

        vmime::shared_ptr<vmime::net::folderStatus> fs = folder->getStatus();                               
        size_t unseen = fs->getUnseenCount();                                                               

        for (size_t sztI = 1; sztI <= unseen; ++sztI)                                                       
        {                                                                                                   
            vmime::shared_ptr<vmime::net::message> msg;                                                     
            std::stringstream ssOutputRaw;                                                                  
            msg = folder->getMessage(static_cast<const int>(sztI));                                         
            vmime::utility::outputStreamAdapter outRaw(ssOutputRaw);                                        
            msg->extract(outRaw);                                                                           
            std::cout << ssOutputRaw.str() << std::endl;                                                    

            folder->deleteMessages(vmime::net::messageSet::byNumber(static_cast<int>(sztI)));               
            folder->expunge(); //explicit expunge                                                           

        }                                                                                                   

        folder->close(true); //Here should an expunge happen, right?                                        
        store->disconnect();                                                                                
        return 0;                                                                                           
    }                                                                                                       
    catch (const vmime::exception& ex){std::cout << "Ex: " << ex.name() << "->" << ex.what() << std::endl;} 
    catch (const std::exception& ex)  {std::cout << "Ex: " << ex.what() << std::endl;}                      
    catch (const std::string& exStr)  {std::cout << "Ex: " << exStr << std::endl;}                          
    catch (...)                       {std::cout << "Ex unknown" << std::endl;}                             
    return 1;                                                                                               
}                                                                                                           

Maybe this the relevant Code-Fragment from the VMIME-Lib?

void IMAPFolder::close(const bool expunge)
{
        shared_ptr <IMAPStore> store = m_store.lock();

        if (!store)
                throw exceptions::illegal_state("Store disconnected");

        if (!isOpen())
                throw exceptions::illegal_state("Folder not open");

        shared_ptr <IMAPConnection> oldConnection = m_connection;

        // Emit the "CLOSE" command to expunge messages marked
        // as deleted (this is fastest than "EXPUNGE")
        if (expunge)
        {
                if (m_mode == MODE_READ_ONLY)
                        throw exceptions::operation_not_supported();

                IMAPCommand::CLOSE()->send(oldConnection);
        }

        // Close this folder connection
        oldConnection->disconnect();

        // Now use default store connection
        m_connection = m_store.lock()->connection();

        m_open = false;
        m_mode = -1;

        m_status = make_shared <IMAPFolderStatus>();

        onClose();
}
vincent-richard commented 9 years ago

Hi!

When you call folder::close() with "expunge" set to true, it won't call the EXPUNGE command, it will simply call the CLOSE command. Here is what the IMAP RFC says about it:

  The CLOSE command permanently removes from the currently selected
  mailbox all messages that have the \Deleted flag set, and returns
  to authenticated state from selected state.  No untagged EXPUNGE
  responses are sent.

So it seems your server does not totally support the CLOSE command, but I couldn't find anything confirming this...

As for marking the messages as seen, simply call message::setFlags() on a single message, or folder::setMessageFlags() for several messages at once, the flag to set is message::FLAG_SEEN.

Vincent

SoulfreezerXP commented 9 years ago

Thnx! Maybe my Server is not correct configured. The explicit "expunge()"- call will work in my special-case :)

SoulfreezerXP commented 9 years ago

Hi Vincent,

now I have another problem here. I call here in my example above the expunge-function explicit. The exchange-server doesn't execute it implicitly on close(), even though the RFC instructs it. I have verified this!

But now, If I have MORE than one mail on the exchange-server, I earn an exception when fetching the second mail. I think when marking the first mail as deleted and expunge it directly, the next sequence-number for the getMessage()-function is invalid or something?! Can you help me again here out? Is it the correct behavior and when yes, how can I workarround this problem? I need the generic for-loop, because I use it in a polymorph way also for the POP3-Accounts! I do not want implement specific code for each protocol-type.


Output: two mails with explicit expunge() on each iteration

imap:2->a004 OK AUTHENTICATE completed. imap:2->(Client)a005 LIST "" "" imap:2->* LIST (\Noselect \HasChildren) "/" "" imap:2->a005 OK LIST completed. imap:1->(Client)a006 CAPABILITY imap:1->* CAPABILITY IMAP4 IMAP4rev1 AUTH=NTLM AUTH=GSSAPI AUTH=PLAIN UIDPLUS CHILDREN IDLE NAMESPACE LITERAL+ imap:1->a006 OK CAPABILITY completed. imap:2->(Client)a006 SELECT INBOX imap:2->* 2 EXISTS imap:2->* 2 RECENT imap:2->* FLAGS (\Seen \Answered \Flagged \Deleted \Draft $MDNSent) imap:2->* OK [PERMANENTFLAGS (\Seen \Answered \Flagged \Deleted \Draft $MDNSent)] Permanent flags imap:2->* OK [UNSEEN 1] Is the first unseen message imap:2->* OK [UIDVALIDITY 102102] UIDVALIDITY value imap:2->* OK [UIDNEXT 26] The next unique identifier value imap:2->a006 OK [READ-WRITE] SELECT completed. imap:2->(Client)a007 CAPABILITY imap:2->* CAPABILITY IMAP4 IMAP4rev1 AUTH=NTLM AUTH=GSSAPI AUTH=PLAIN UIDPLUS CHILDREN IDLE NAMESPACE LITERAL+ imap:2->a007 OK CAPABILITY completed. imap:2->(Client)a008 STATUS INBOX (MESSAGES UNSEEN UIDNEXT UIDVALIDITY) imap:2->* STATUS INBOX (MESSAGES 2 UNSEEN 2 UIDNEXT 26 UIDVALIDITY 102102) imap:2->a008 OK STATUS completed.

(MY DEBUG --> value of unseen-variable: 2)

imap:2->(Client)a009 FETCH 1 BODY[] imap:2->* 1 FETCH (BODY[] {2467} imap:2->{...2467 bytes of data...} imap:2-> FLAGS (\Seen \Recent)) imap:2->a009 OK FETCH completed. Received: from xxxx.xxxx.de ([fe80:8:bbee:53b3]) by xxxx.xxxx.de ([fe80::d9fd:bee:53b3%14]) with mapi id 14.03.0123.003; Mon, 11 May 2015 15:37:32 +0200 From: "xxx" To: EMSTEST xxx@xxx.de Subject: Test1 Thread-Topic: Test1 Thread-Index: AdCL76jfe/oHK9eVg== Date: Mon, 11 May 2015 15:37:31 +0200 Message-ID: 0EFEACC1F0970F@xxxx.xxxx.de Accept-Language: de-DE, en-US Content-Language: de-DE X-MS-Exchange-Organization-AuthAs: Internal X-MS-Exchange-Organization-AuthMechanism: 04 X-MS-Exchange-Organization-AuthSource: xxxx.xxxx.de X-MS-Has-Attach: X-MS-Exchange-Organization-SCL: -1 X-MS-TNEF-Correlator: Content-Type: text/html; charset="us-ascii" MIME-Version: 1.0

.... ....

imap:2->(Client)a010 STORE 1 +FLAGS (\Deleted) imap:2->* 1 FETCH (FLAGS (\Seen \Deleted \Recent)) imap:2->a010 OK STORE completed. imap:2->(Client)a011 EXPUNGE imap:2->* 1 EXPUNGE imap:2->* 1 EXISTS imap:2->a011 OK EXPUNGE completed. imap:2->(Client)a012 LOGOUT imap:2->(Client)Disconnecting imap:1->(Client)a007 LOGOUT imap:1->(Client)Disconnecting Ex: message_not_found->Message not found.