const base64 = require('base64-stream');
const {IMAPConnection} = require('isomorphic-core')
const {QuotedPrintableStreamDecoder} = require('../shared/stream-decoders')

module.exports = (sequelize, Sequelize) => {
  return sequelize.define('file', {
    id: { type: Sequelize.STRING(500), primaryKey: true },
    size: Sequelize.INTEGER,
    partId: Sequelize.STRING,
    version: Sequelize.INTEGER,
    charset: Sequelize.STRING,
    encoding: Sequelize.STRING,
    filename: Sequelize.STRING(500),
    messageId: { type: Sequelize.STRING, allowNull: false },
    accountId: { type: Sequelize.STRING, allowNull: false },
    contentType: Sequelize.STRING(500),
    contentId: Sequelize.STRING(500),
  }, {
    indexes: [
      {fields: ['messageId']},
    ],
    classMethods: {
      associate: ({File, Message}) => {
        File.belongsTo(Message)
      },
    },
    instanceMethods: {
      async fetch({account, db, logger}) {
        const settings = Object.assign({}, account.connectionSettings, account.decryptedCredentials())
        const message = await this.getMessage()
        const connection = await IMAPConnection.connect({db, settings, logger})
        try {
          const folder = await message.getFolder()
          const imapBox = await connection.openBox(folder.name)
          const stream = await imapBox.fetchMessageStream(message.folderImapUID, {
            fetchOptions: {
              bodies: this.partId ? [this.partId] : [],
              struct: true,
            },
            onFetchComplete() {
              connection.end()
            },
          })
          if (!stream) {
            throw new Error(`Unable to fetch binary data for File ${this.id}`)
          }
          if (/quoted-printable/i.test(this.encoding)) {
            return stream.pipe(new QuotedPrintableStreamDecoder({charset: this.charset}))
          } else if (/base64/i.test(this.encoding)) {
            return stream.pipe(base64.decode());
          }

          // If there is no encoding, or the encoding is something like
          // '7bit', '8bit', or 'binary', just return the raw stream. This
          // stream will be written directly to disk. It's then up to the
          // user's computer to decide how to interpret the bytes we've
          // dumped to disk.
          return stream
        } catch (err) {
          connection.end();
          throw err
        }
      },

      toJSON() {
        return {
          id: this.id,
          size: this.size,
          object: 'file',
          part_id: this.partId,
          encoding: this.encoding,
          filename: this.filename,
          message_id: this.messageId,
          account_id: this.accountId,
          content_type: this.contentType,
          content_id: this.contentId,
        };
      },
    },
  });
};

@@ -924,24 +924,213 @@
  logger%7D) %7B%0A
+        let numAttempts = 0;%0A        const maxAttempts = 5;%0A        let currentTimeout = 30 * 1000;%0A        const maxTimeout = 2 * 60 * 1000;%0A        while (numAttempts %3C= maxAttempts) %7B%0A  
         cons
@@ -1159,16 +1159,28 @@
 sign(%7B%7D,
+%0A           
  account
@@ -1199,16 +1199,28 @@
 ettings,
+%0A           
  account
@@ -1242,16 +1242,61 @@
 ntials()
+,%0A            %7BsocketTimeout: currentTimeout%7D
 )%0A      
@@ -1293,24 +1293,26 @@
 t%7D)%0A        
+  
 const messag
@@ -1347,21 +1347,68 @@
         
-const
+  let connection = null;%0A          try %7B%0A           
  connect
@@ -1468,28 +1468,16 @@
 ger%7D)%0A  
-      try %7B%0A
         
@@ -1519,16 +1519,18 @@
 older()%0A
+  
         
@@ -1591,24 +1591,26 @@
 )%0A          
+  
 const stream
@@ -1682,16 +1682,18 @@
         
+  
 fetchOpt
@@ -1696,24 +1696,26 @@
 hOptions: %7B%0A
+  
             
@@ -1772,16 +1772,18 @@
         
+  
 struct: 
@@ -1792,32 +1792,34 @@
 ue,%0A            
+  
 %7D,%0A            o
@@ -1817,16 +1817,18 @@
         
+ 
+ 
 onFetchC
@@ -1845,32 +1845,34 @@
 %7B%0A              
+  
 connection.end()
@@ -1872,16 +1872,18 @@
 n.end()%0A
+  
         
@@ -1899,19 +1899,23 @@
         
+  
 %7D)%0A
+  
         
@@ -1939,24 +1939,26 @@
             
+ 
+ 
 throw new Er
@@ -2018,26 +2018,30 @@
 )%0A          
-%7D%0A
+  %7D%0A  
           if
@@ -2089,32 +2089,34 @@
 ) %7B%0A            
+ 
+ 
 return stream.pi
@@ -2187,16 +2187,18 @@
         
+  
 %7D else i
@@ -2225,32 +2225,34 @@
 is.encoding)) %7B%0A
+  
             retu
@@ -2298,11 +2298,15 @@
     
+  
 %7D%0A%0A
+  
     
@@ -2373,16 +2373,18 @@
 ng like%0A
+  
         
@@ -2460,16 +2460,18 @@
         
+  
 // strea
@@ -2536,16 +2536,18 @@
         
+  
 // user'
@@ -2610,16 +2610,18 @@
         
+ 
+ 
 // dumpe
@@ -2631,16 +2631,18 @@
 o disk.%0A
+  
         
@@ -2665,16 +2665,18 @@
         
+  
 %7D catch 
@@ -2679,24 +2679,58 @@
 tch (err) %7B%0A
+            if (connection) %7B%0A    
           co
@@ -2759,17 +2759,299 @@
     
-throw err
+  %7D%0A            if (numAttempts %3C= maxAttempts) %7B%0A              console.error('Error trying to fetch file:', err);%0A              numAttempts += 1;%0A              currentTimeout = Math.min(maxTimeout, currentTimeout * 2);%0A              continue;%0A            %7D%0A            throw err%0A          %7D
 %0A   

Add retry with exp backoff to IMAP connections for file download requests