"""Database class.

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
"""

import logging
import os
import time

from cloudant.client import CouchDB
from cloudant.result import Result
from datetime import datetime


class Database:
    db_prefix = os.getenv('DB_PREFIX', '')
    dbname = db_prefix + 'ow_kafka_triggers'

    username = os.environ['DB_USER']
    password = os.environ['DB_PASS']
    url = os.environ['DB_URL']

    client = CouchDB(username, password, url=url)
    client.connect()

    filters_design_doc_id = '_design/filters'
    only_triggers_view_id = 'only-triggers'

    if dbname in client.all_dbs():
        logging.info('Database exists - connecting to it.')
        database = client[dbname]
    else:
        logging.warn('Database does not exist - creating it.')
        database = client.create_database(dbname)


    def disableTrigger(self, triggerFQN, status_code):
        try:
            document = self.database[triggerFQN]

            if document.exists():
                logging.info('Found trigger to disable from DB: {}'.format(triggerFQN))

                status = {
                    'active': False,
                    'dateChanged': time.time(),
                    'reason': {
                        'kind': 'AUTO',
                        'statusCode': status_code,
                        'message': 'Automatically disabled after receiving a {} status code when firing the trigger.'.format(status_code)
                    }
                }

                document['status'] = status
                document.save()

                logging.info('{} Successfully recorded trigger as disabled.'.format(triggerFQN))
        except Exception as e:
            logging.error('[{}] Uncaught exception while disabling trigger: {}'.format(triggerFQN, e))

    def changesFeed(self, timeout, since=None):
        if since == None:
            return self.database.infinite_changes(include_docs=True, heartbeat=(timeout*1000))
        else:
            return self.database.infinite_changes(include_docs=True, heartbeat=(timeout*1000), since=since)

    def createCanary(self):
        retryCount = 3

        while retryCount >= 0:
            if retryCount == 0:
                logging.error('[canary] Retried and failed {} times to create a canary'.format(retryCount))
            else:
                try:
                    document = dict()
                    document['canary'] = datetime.now().isoformat()

                    result = self.database.create_document(document)
                    logging.info('[canary] Successfully wrote canary to DB')

                    return result
                except Exception as e:
                    retryCount -= 1
                    logging.error('[canary] Uncaught exception while recording trigger to database: {}'.format(e))

    def deleteDoc(self, docId):
        try:
            document = self.database[docId]

            if document.exists():
                document.delete()
                logging.debug('[database] Successfully deleted document from DB: {}'.format(docId))
            else:
                logging.warn('[database] Attempted to delete non-existent document from DB: {}'.format(docId))
        except Exception as e:
            logging.error('[database] Uncaught exception while deleting document {} from database: {}'.format(docId, e))

    def migrate(self):
        logging.info('Starting DB migration')

        filtersDesignDoc = self.database.get_design_document(self.filters_design_doc_id)

        if not filtersDesignDoc.exists():
            logging.info('Creating the design doc')

            # create only-triggers view
            self.database.create_document({
                '_id': self.filters_design_doc_id,
                'views': {
                    self.only_triggers_view_id: {
                        'map': """function (doc) {
                                    if(doc.triggerURL) {
                                        emit(doc._id, 1);
                                    }
                                }"""
                    }
                }
            })
        else:
            logging.info("design doc already exists")

        logging.info('Database migration complete')

@@ -2885,32 +2885,31 @@
     
-retryCount
+maxRetries
  = 3%0A
-%0A
         
 whil
@@ -2904,22 +2904,16 @@
         
-while 
 retryCou
@@ -2919,26 +2919,21 @@
 unt 
-%3E
 = 0
-:%0A    
+%0A%0A
         
 if r
@@ -2928,18 +2928,21 @@
         
-if
+while
  retryCo
@@ -2949,138 +2949,20 @@
 unt 
-== 0:%0A                logging.error('%5Bcanary%5D Retried and failed %7B%7D times to create a canary'.format(retryCount))%0A            else
+%3C maxRetries
 :%0A  
@@ -2967,37 +2967,29 @@
             
- 
-   
 try:%0A
-    
             
@@ -3018,36 +3018,32 @@
                 
-  
-  
 document%5B'canary
@@ -3071,28 +3071,24 @@
 soformat()%0A%0A
-    
             
@@ -3148,36 +3148,32 @@
                 
-   
- 
 logging.info('%5Bc
@@ -3226,28 +3226,24 @@
             
-    
 return resul
@@ -3244,20 +3244,16 @@
  result%0A
-    
         
@@ -3291,28 +3291,24 @@
             
-    
 retryCount -
@@ -3310,17 +3310,13 @@
 unt 
--
++
 = 1%0A
-    
     
@@ -3419,24 +3419,125 @@
 format(e))%0A%0A
+        logging.error('%5Bcanary%5D Retried and failed %7B%7D times to create a canary'.format(maxRetries))%0A%0A
     def dele

Fix infinite loop in error handling (#194)