Completed
Pull Request — master (#189)
by Marek
02:07
created

ige.MsgMngr   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 161
dl 0
loc 235
rs 7.9487
c 0
b 0
f 0
wmc 52

28 Methods

Rating   Name   Duplication   Size   Complexity  
A MsgMngr.backup() 0 2 1
A MsgMngr.checkpoint() 0 2 1
A MsgMngr.shutdown() 0 3 1
A MsgMngr.clear() 0 2 1
A MsgMngr.__init__() 0 4 1
C Mailbox.deleteOld() 0 24 7
A MailboxRoot.getAll() 0 2 1
A Mailbox.deleteAll() 0 2 1
A MsgMngr.trashUnusedMailboxes() 0 13 4
A MsgMngr.getMailbox() 0 21 4
A Mailbox.__init__() 0 6 1
A MsgMngr.delete() 0 3 1
A MsgMngr.upgrade() 0 2 1
A Mailbox.setDatabase() 0 2 1
A Mailbox.add() 0 13 3
A MailboxRoot.removeMailbox() 0 2 1
A MsgMngr.get() 0 3 1
A MailboxRoot.addMailbox() 0 3 2
A Mailbox.delete() 0 7 2
A Mailbox.get() 0 11 4
A MsgMngr.deleteOld() 0 8 2
A MsgMngr.send() 0 3 1
A MailboxRoot.scan() 0 10 3
A Mailbox.upgrade() 0 2 1
A MailboxRoot.__init__() 0 2 1
A MsgMngr.getMailboxRoot() 0 7 2
A MsgMngr.getMailboxes() 0 7 2
A Mailbox.__getstate__() 0 3 1

How to fix   Complexity   

Complexity

Complex classes like ige.MsgMngr often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Outer Space.
5
#
6
#  Outer Space is free software; you can redistribute it and/or modify
7
#  it under the terms of the GNU General Public License as published by
8
#  the Free Software Foundation; either version 2 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Outer Space is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
#
16
#  You should have received a copy of the GNU General Public License
17
#  along with Outer Space; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import time, log, ige
22
23
class MsgMngrException(Exception):
24
    pass
25
26
class MsgMngr:
27
28
    MAILBOXROOT_ID = "#MAILBOXROOT#"
29
30
    def __init__(self, database):
31
        # init object space root
32
        self.database = database
33
        self.deleted = 0
34
35
    def shutdown(self):
36
        log.message('Shutdown')
37
        self.database.shutdown()
38
39
    def checkpoint(self):
40
        self.database.checkpoint()
41
42
    def clear(self):
43
        self.database.clear()
44
45
    def backup(self, basename):
46
        self.database.backup(basename)
47
48
    def upgrade(self):
49
        return
50
51
    def getMailboxRoot(self):
52
        mailboxRoot = self.database.get(self.MAILBOXROOT_ID, None)
53
        if not mailboxRoot:
54
            mailboxRoot = MailboxRoot()
55
            mailboxRoot.scan(self.database)
56
            self.database.create(mailboxRoot, id = self.MAILBOXROOT_ID)
57
        return mailboxRoot
58
59
    def getMailbox(self, gameID, oid, recreate = True):
60
        name = "%s-%s" % (gameID, oid)
61
        try:
62
            mailbox = self.database.get(name, None)
63
        except:
64
            log.warning("Cannot read the mailbox", name)
65
            # force recreation
66
            mailbox = None
67
        if mailbox:
68
            #if hasattr(mailbox, "setDatabase"):
69
            mailbox.setDatabase(self.database)
70
            return mailbox
71
        if not recreate:
72
            raise MsgMngrException("No such mailbox")
73
        # create mailbox
74
        log.message("Creating mailbox", name)
75
        mailbox = Mailbox(name)
76
        self.database.create(mailbox, id = name)
77
        mailbox.setDatabase(self.database)
78
        self.getMailboxRoot().addMailbox(name)
79
        return mailbox
80
81
    # send message
82
    def send(self, gameID, oid, messageDict):
83
        self.getMailbox(gameID, oid).add(messageDict)
84
        return 1
85
86
    # get messages from mailbox
87
    def get(self, gameID, oid, lastID):
88
        response = self.getMailbox(gameID, oid).get(lastID)
89
        return response
90
91
    # delete messages from mailbox
92
    def delete(self, gameID, oid, msgIDs):
93
        self.getMailbox(gameID, oid).delete(msgIDs)
94
        return 1
95
96
    # delete old messages from mailbox
97
    def deleteOld(self, gameID, oid, forum, maxAge):
98
        #@log.debug("Compresing mailbox", gameID, oid, forum)
99
        self.deleted += self.getMailbox(gameID, oid).deleteOld(forum, maxAge)
100
        if self.deleted >= 200:
101
            self.database.checkpoint()
102
            self.deleted = 0
103
        #@log.debug("Compression finished", gameID, oid, forum)
104
        return 1
105
106
    # delete unused mailboxes
107
    def trashUnusedMailboxes(self, mailboxes):
108
        trash = self.getMailboxes()
109
        #@log.debug("Mailboxes:", trash)
110
        #@log.debug("Used:", mailboxes)
111
        for mailbox in mailboxes:
112
            if mailbox in trash:
113
                trash.remove(mailbox)
114
        for gameID, oid in trash:
115
            log.message("Removing mailbox", gameID, oid)
116
            box = self.getMailbox(gameID, oid)
117
            box.deleteAll()
118
            del self.database[box.name]
119
            self.getMailboxRoot().removeMailbox(box.name)
120
121
    def getMailboxes(self):
122
        mailboxes = self.getMailboxRoot().getAll()
123
        result = []
124
        for mailbox in mailboxes:
125
            gameID, oid = mailbox.split("-")
126
            result.append((gameID, int(oid)))
127
        return result
128
129
class MailboxRoot:
130
131
    def __init__(self):
132
        self.mailboxNames = []
133
134
    def scan(self, db):
135
        log.debug("*** SCANNING MAILBOXES ***")
136
        self.mailboxNames = []
137
        for key in db.keys():
138
            if key.count("-") == 1:
139
                # mailbox
140
                self.mailboxNames.append(key)
141
            else:
142
                #skip ordinal mails and mailboxroot
143
                continue
144
145
    def addMailbox(self, name):
146
        if name not in self.mailboxNames:
147
            self.mailboxNames.append(name)
148
149
    def removeMailbox(self, name):
150
        self.mailboxNames.remove(name)
151
152
    def getAll(self):
153
        return self.mailboxNames
154
155
class Mailbox:
156
157
    def __init__(self, name):
158
        self.oid = 0
159
        self.name = name
160
        self.msgIds = 0
161
        self.messageIDs = []
162
        self.database = None
163
164
    def setDatabase(self, database):
165
        self.database = database
166
167
    def add(self, message, msgID = None):
168
        if msgID == None:
169
            msgID = self.msgIds
170
            self.msgIds += 1
171
        dbID = "%s-%d" % (self.name, msgID)
172
        message['time'] = time.time()
173
        message['dbID'] = dbID
174
        message['id'] = msgID
175
        self.messageIDs.append(msgID)
176
        try:
177
            self.database.create(message, dbID)
178
        except ige.ServerException:
179
            log.warning("Message not added to the database")
180
181
    def get(self, lastID = -1):
182
        result = []
183
        # are there any new messages?
184
        if lastID + 1 == self.msgIds:
185
            return result
186
        # create list of new messages
187
        result = [
188
            self.database["%s-%d" % (self.name, msgID)]
189
            for msgID in self.messageIDs if msgID > lastID
190
        ]
191
        return result
192
193
    def delete(self, msgIDs):
194
        for msgID in msgIDs:
195
            dbID = "%s-%d" % (self.name, msgID)
196
            #@log.debug("MsgMngr - deleting message", dbID)
197
            del self.database[dbID]
198
            self.messageIDs.remove(msgID)
199
        return 1
200
201
    def deleteAll(self):
202
        self.delete(self.messageIDs)
203
204
    def deleteOld(self, forum, maxAge):
205
        now = time.time()
206
        maxAge = maxAge * 24 * 60 * 60 # maxAge is in days
207
        delete = []
208
        for msgID in self.messageIDs:
209
            message = self.database["%s-%d" % (self.name, msgID)]
210
            if message['forum'] == forum and message['time'] + maxAge < now:
211
                #@log.debug("MsgMngr - deleting msg", self.name, forum, msgID)
212
                delete.append(msgID)
213
            elif message['forum'] == forum:
214
                break
215
        if not delete:
216
            return 0
217
        log.debug("MsgMngr - deleting %d messages (500 max) out of %d from %s/%s" % (len(delete), len(self.messageIDs), self.name, forum))
218
        t0 = time.time()
219
        maxMsg = 500
220
        count = min(len(delete), maxMsg)
221
        self.delete(delete[:maxMsg])
222
        t = time.time() - t0
223
        if t > 0.0:
224
            log.debug("MsgMngr - deleted %.3f msgs/sec" % (count / t))
225
        else:
226
            log.debug("MsgMngr - deleted NaN msgs/sec")
227
        return count
228
229
    def __getstate__(self):
230
        self.database = None
231
        return self.__dict__
232
233
    def upgrade(self):
234
        log.debug("Up-to-date")
235