예제 #1
0
    def _handleFailed(self, con):
        c = con.cursor()
        c.execute("SELECT * FROM {} WHERE state=?".format(self._name),
                  (self.OUTBOX_FAILED,))
        for msg in c.fetchall():
            if not self._online():
                return
            
            # strip items and meat
            message = decode(msg['data'])
            message['items'] = []
            message['meat'] = 0
            success = False
            exc = None
            try:
                # try resending without items/meat
                if message != decode(msg['data']):
                    self._sendKmail(msg['id'], message, 
                                    sendItemWarning=True)
                    success = True
                    self._log.debug("Sent item retention warning for message "
                                    "{}.".format(msg['id']))
                else:
                    self._log.debug("Message {} has no items; skipping "
                                    "retention.".format(msg['id']))

            except kol.Error.Error as e:
                if not self._online():
                    # no point sending, we got logged out!
                    return
                self._log.debug("Error sending retention message for kmail {}."
                                .format(msg['id']))
                exc = e
                
                
            if success:
                with con:
                    # set to withheld state
                    c2 = con.cursor()
                    c2.execute("UPDATE {} SET state=?, error=? WHERE id=?"
                               .format(self._name),
                               (self.OUTBOX_WITHHELD, None, msg['id']))
                    self._log.info("Kmail {} state set to OUTBOX_WITHHELD"
                                   .format(msg['id']))
            else:
                with con:
                    # set to could not send state
                    code = exc.code if exc is not None else msg['error']
                    c2 = con.cursor()
                    c2.execute("UPDATE {} SET state=?, error=? WHERE id=?"
                               .format(self._name),
                               (self.OUTBOX_COULDNOTSEND, code, msg['id']))
                    self._log.info("Kmail {} state set to OUTBOX_COULDNOTSEND "
                                   "due to error {}".format(msg['id'], exc))
예제 #2
0
    def getNextKmail(self):
        """ Get the next Kmail. The state of this kmail is set to RESPONDING.
        You will need to call the respondToKmail() method to set that it has
        been processed, even if you do nothing.
        """
        con = self._db.getDbConnection(isolation_level="IMMEDIATE")
        try:
            with con:
                c = con.cursor()
                c.execute("SELECT * FROM {} WHERE state=? ORDER BY id ASC "
                          .format(self._name), (self.INBOX_READY,))
                msgAccepted = False
                while not msgAccepted:
                    # loop until we get an "acceptable" message
                    # messages are only unacceptable if they have items while
                    # we are in HC/Ronin
                    msg = c.fetchone()
                    if msg is None:
                        return None # no new messages

                    # we got a message! Are we online?
                    if not self._online():
                        return None
                    
                    # can we receive items?
                    getItems = self.canReceiveItems()

                    if getItems:
                        msgAccepted = True # accept all messages
                    else:
                        message = decode(msg['data'])
                        
                        # message accepted only if it does not have items
                        msgAccepted = not message.get('items', {})
                            
                c.execute("UPDATE {} SET state=? WHERE id=?"
                          .format(self._name),
                          (self.INBOX_RESPONDING, msg['id']))
                message = decode(msg['data'])

                # remove unicode characters
                if 'text' in message:
                    txt = message['text']
                    txtUnicode = u''.join(unichr(ord(c)) for c in txt)
                    txtUnicode = self._htmlParser.unescape(txtUnicode)
                    message['text'] = unidecode(txtUnicode)

                
                self._log.debug("Kmail ready -> responding: {}"
                                .format(msg['id']))
                return message
        finally:
            con.close()
예제 #3
0
 def sendDeferredItems(self, uid):
     """ Send deferred kmail items+meat to user."""
     con = self._db.getDbConnection()
     try:
         c = con.cursor()
         with con:
             c.execute("SELECT * FROM {} WHERE state=? AND userId=? "
                       "ORDER BY id ASC LIMIT 2"
                       .format(self._name), (self.OUTBOX_DEFERRED, uid))
             rows = c.fetchall()
             if len(rows) == 0:
                 raise IndexError("No deferred kmails for user {}"
                                  .format(uid))
             elif len(rows) > 1:
                 raise Exception("Multiple deferred kmails detected for "
                                 "user {}".format(uid))
             msg = rows[0]
             message = decode(msg['data'])
             mid = msg['id']
             self._log.info("Sending deferred kmail to user {}"
                            .format(uid))
             
             # unreserve items from inventory
             self._invMan.clearReservationsWtihDbCursor(
                 self._name, mid, c)
             # send new kmail
             self._insertSplitKmail(c, self.OUTBOX_SENDING, message, 
                                    reserveItems=True)
             c.execute("DELETE FROM {} WHERE id=?".format(self._name),
                       (mid,))
             self._event.set()
     finally:
         con.close()        
예제 #4
0
 def _checkStock(self):
     with InventoryLock.lock:
         self._invMan.refreshInventory()
         inv = self._invMan.completeInventory()
         r = StatusRequest(self._s)
         d = tryRequest(r, numTries=6, initialDelay=3, scaleFactor=1.25)
         meat = int(d['meat'])
         itemsOwed = {}
         meatOwed = 0
         
         con = self._db.getDbConnection()
         c = con.cursor()
         c.execute("SELECT * FROM {}".format(self._name))
         msg = c.fetchone()
         while msg is not None:
             if msg['state'] in [self.OUTBOX_SENDING,
                                 self.OUTBOX_DEFERRED]:
                 message = decode(msg['data'])
                 itemsOwed = _itemsToDict(message.get('items', []), 
                                          itemsOwed)
                 meatOwed += message.get('meat', 0)
             msg = c.fetchone()
         difference = dict((iid, inv.get(iid, 0) - qty)
                           for iid,qty in itemsOwed.items())
         deficit = dict((iid, -diff) for iid,diff in difference.items()
                        if diff < 0)
         if deficit:
             # get items in display case
             r2 = GetDisplayCaseRequest(self._s)
             d2 = tryRequest(r2)
             display = _itemsToDict(d2['items'])
             difference = dict(
                 (iid, inv.get(iid, 0) + display.get(iid, 0) - qty)
                 for iid,qty in itemsOwed.items())
             deficit = dict((iid, -diff) for iid,diff in difference.items()
                            if diff < 0)
         if deficit or meatOwed > meat:
             # notify admins of item deficit!
             warningText = ("Warning: {} has an item deficit of: \n"
                            .format(self._props.userName))
             for iid, d in deficit.items():
                 warningText += ("\n{}: {}"
                                 .format(
                                     d, getItemFromId(iid).get(
                                         'name', "item ID {}".format(iid))))
             if meatOwed > meat:
                 warningText += "\n{} meat".format(meatOwed-meat)
             with con:
                 c2 = con.cursor()
                 for adminUid in self._props.getAdmins("mail_fail"):
                     # notify admins of deficit
                     
                     newMsg = {'userId': adminUid, 'meat': 0,
                               'text': warningText, 'items': []}
                     self._insertSplitKmail(c2, self.OUTBOX_SENDING, 
                                            newMsg, reserveItems=False)
예제 #5
0
 def _handleCouldNotSend(self, con):
     c = con.cursor()
     c.execute("SELECT * FROM {} WHERE state=?".format(self._name),
               (self.OUTBOX_COULDNOTSEND,))
     for msg in c.fetchall():
         if not self._online():
             return
         message = decode(msg['data'])
         uid = msg['userId']
         mid = msg['id']
         with con:
             c2 = con.cursor()
             self._log.info("Mail could not send: {}. "
                            "Notifying admins..."
                            .format(mid))
             if "mail_fail" not in self._props.getPermissions(uid):
                 for adminUid in self._props.getAdmins("mail_fail"):
                     # notify admins of failure
                     newMsg = {'userId': adminUid, 
                               'text': "NOTICE: The following kmail "
                                       "failed to send: {}"
                                       .format(message),
                               'meat': 0, 'items': []}
                     self._insertSplitKmail(c2, self.OUTBOX_SENDING, 
                                            newMsg, reserveItems=False)
             else:
                 self._log.error("Error dispatching notification to "
                                 "administrator: {}".format(message))
                 
             # mark as withheld if any items are there
             newState = self.ERROR
             errorCode = msg['error']
             newError = None
             if message['items']:
                 newState = self.OUTBOX_WITHHELD
                 if errorCode not in _withholdItemErrors:
                     # unreserve items. There's no use holding on to them; 
                     # we will never be able to recover. It's probably an
                     # invalid user ID anyway.
                     newState = self.HANDLED
                     newError = errorCode
                     self._invMan.clearReservationsWtihDbCursor(
                                                 self._name, mid, c2)
                     self._log.warning("Error trying to send kmail {}; "
                                       "giving up and releasing items."
                                       .format(message))
                 # also copy message as ERROR
                 c2.execute("INSERT INTO {0}"
                                 "(state, userId, data, error, kmailId) "
                            "SELECT ?, userId, data, error, id FROM {0} "
                            "WHERE id=?".format(self._name),
                            (self.ERROR, mid))
             c2.execute(
                 "UPDATE {} SET state=?, error=? WHERE id=?"
                 .format(self._name), (newState, newError, mid))
예제 #6
0
 def _deleteDownloadedKmails(self, con):
     c = con.cursor()
     c.execute("SELECT * FROM {} WHERE state=?".format(self._name),
               (self.INBOX_DOWNLOADED,))
     for msg in c.fetchall():
         self._deleteKmail(decode(msg['data']))
         with con:
             c2 = con.cursor()
             c2.execute("UPDATE {} SET state=? WHERE id=?"
                        .format(self._name),
                        (self.INBOX_READY, msg['id']))
예제 #7
0
 def getDeferredItems(self, uid):
     """ Send deferred kmail items+meat to user. Items are pulled out
     of the closet. """
     con = self._db.getDbConnection()
     try:
         c = con.cursor()
         with con:
             c.execute("SELECT * FROM {} WHERE state=? AND userId=? "
                       "ORDER BY id ASC LIMIT 2"
                       .format(self._name), (self.OUTBOX_DEFERRED, uid))
             rows = c.fetchall()
             if len(rows) == 0:
                 return (0, {})
             elif len(rows) > 1:
                 raise Exception("Multiple deferred kmails detected for "
                                 "user {}".format(uid))
             message = decode(rows[0]['data'])
             return (message['meat'], _itemsToDict(message['items']))
     finally:
         con.close()        
예제 #8
0
    def _handleWithheld(self, con):
        c = con.cursor()
        c.execute("SELECT * FROM {} WHERE state=?".format(self._name),
                  (self.OUTBOX_WITHHELD,))
        for msg in c.fetchall():
            if not self._online():
                return
            # kmail was withheld. We need to create a "deferred" kmail
            # with the items.
            uid = msg['userId']
            message = decode(msg['data'])
            mid = msg['id']
            if message.get('meat', 0) == 0:
                if len(message.get('items', [])) == 0:
                    continue
            c2 = con.cursor()
            # find if there is already a deferred message with items/meat
            c2.execute("SELECT * FROM {} WHERE state=? AND userId=? "
                       "ORDER BY id ASC LIMIT 2"
                       .format(self._name), 
                       (self.OUTBOX_DEFERRED, uid))
            rows = c2.fetchall()
            if len(rows) == 0:
                # set to deferred
                message['text'] = _deferredText
                with con:
                    c2.execute("UPDATE {} SET data=?, state=? WHERE id=?"
                               .format(self._name),
                               (encode(message), self.OUTBOX_DEFERRED, mid))
                    self._log.info("Retained items for message {} from "
                                   "outbox. State set to DEFERRED."
                                   .format(msg['id']))

            elif len(rows) == 1:
                # merge items with old deferred message
                message2 = decode(rows[0]['data'])
                mid2 = rows[0]['id']
                itemQtyDict = _itemsToDict(message['items'])
                itemQtyDictMerge = _itemsToDict(message2['items'], itemQtyDict)
                message2['items'] = _itemsToList(itemQtyDictMerge)
                # merge meat
                message2['meat'] += message['meat']
                with con:
                    c2.execute("UPDATE {} SET data=? WHERE id=?"
                               .format(self._name),
                               (encode(message2), mid2))
                    c2.execute("DELETE FROM {} WHERE id=?".format(self._name),
                               (mid,))
                    self._log.info("Retained items for message {} from "
                                   "outbox (merged). State set to DEFERRED."
                                   .format(mid))
                    
                    # merge item reservations
                    self._invMan.clearReservationsWtihDbCursor(
                        self._name, mid, c2)
                    self._invMan.clearReservationsWtihDbCursor(
                        self._name, mid2, c2)
                    self._invMan.reserveItemsWithDbCursor(
                        itemQtyDictMerge, self._name, mid2, c2)
                    
            else:
                raise Exception("Found more than one deferred kmail: "
                                "{}".format(', '.join(decode(msg['data']))
                                            for msg in rows))
예제 #9
0
 def _handleSending(self, con):
     c = con.cursor()
     c.execute("SELECT * FROM {} WHERE state=? ORDER BY id ASC"
               .format(self._name),
               (self.OUTBOX_SENDING,))
     sentTimes = {}
     for msg in c.fetchall():
         message = decode(msg['data'])
         self._log.debug("Sending message {}: {}".format(msg['id'],
                                                         message))
         if not self._online():
             return
         success = False
         exc = None
         try:
             while time.time() - sentTimes.get(message['userId'], 0) < 2:
                 time.sleep(0.1)
             self._sendKmail(msg['id'], message)
             sentTimes[message['userId']] = time.time()
             success = True
         except kol.Error.Error as e:
             if not self._online():
                 return
             exc = e
         
         if success:
             with con:
                 c2 = con.cursor()
                 c2.execute("UPDATE {} SET state=? WHERE id=?"
                            .format(self._name),
                            (self.OUTBOX_TODELETE, msg['id']))
                 self._log.info("Message {} sent.".format(msg['id']))
         else:
             with con:
                 c2 = con.cursor()
                 if msg['itemsOnly'] != 1:
                     if (not message['items']) and message['meat'] == 0:
                         # message failed, do not resend
                         c2.execute(
                             "UPDATE {} SET state=?, error=? WHERE id=?"
                             .format(self._name), (self.OUTBOX_COULDNOTSEND, 
                                                   exc.code, msg['id']))
                         self._log.info("Failed to send message {}: {}. "
                                        "State set to OUTBOX_COULDNOTSEND."
                                        .format(msg['id'], exc))
                     else:
                         # try sending without any items
                         c2.execute(
                             "UPDATE {} SET state=?, error=? WHERE id=?"
                             .format(self._name), 
                             (self.OUTBOX_FAILED, exc.code, msg['id']))
                         self._log.debug("Failed to send message {}: {}. "
                                         "State set to OUTBOX_FAILED."
                                         .format(msg['id'], exc))
                 else:
                     # just add to withheld list (do not send failure msg)
                     # NOTE: itemsOnly is set to 1 for a message
                     # continuation. This way, users will only receive
                     # once "message failed to send" kmail.
                     c2.execute("UPDATE {} SET state=?, error=? WHERE id=?"
                                .format(self._name),
                                (self.OUTBOX_WITHHELD, exc.code, msg['id']))