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))
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()
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()
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)
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))
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']))
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()
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))
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']))